From c2c480ed34c5940e4763c3606ad60855e86080e6 Mon Sep 17 00:00:00 2001 From: Vaceslav Ustinov Date: Thu, 20 Nov 2025 23:39:54 +0100 Subject: [PATCH 1/3] style: enforce code quality rules via .editorconfig MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Configure .editorconfig to enforce code style as warnings: - IDE0011: Require braces for all if statements - IDE0040: Require explicit accessibility modifiers - IDE1006: Enforce naming conventions (private fields with _ prefix) Apply formatting fixes across codebase: - Fix line endings (CRLF→LF) in GUI project files - Add braces to single-line if statements - Add accessibility modifiers where missing - Format code according to all .editorconfig rules Changes applied via dotnet format. All 690 tests passing. Build now completes with zero warnings. --- .editorconfig | 10 + .../Analyzers/TemplateAnalyzer.cs | 5 +- .../Converters/RepeatingConverter.cs | 5 +- TriasDev.Templify.Demo/Program.cs | 55 +- .../StirlingPdfConverter.cs | 5 +- TriasDev.Templify.Gui/App.axaml.cs | 172 +++--- TriasDev.Templify.Gui/Program.cs | 42 +- .../TriasDev.Templify.Gui.csproj | 66 +-- TriasDev.Templify.Gui/ViewLocator.cs | 68 +-- .../ViewModels/MainWindowViewModel.cs | 554 +++++++++--------- .../ViewModels/ViewModelBase.cs | 14 +- .../Views/MainWindow.axaml.cs | 22 +- .../Visitors/DocumentWalkerTests.cs | 17 +- .../Conditionals/ConditionalEvaluator.cs | 11 +- .../Expressions/BooleanExpression.cs | 17 +- .../Expressions/BooleanExpressionParser.cs | 27 +- 16 files changed, 586 insertions(+), 504 deletions(-) diff --git a/.editorconfig b/.editorconfig index d409a54..91c70fd 100644 --- a/.editorconfig +++ b/.editorconfig @@ -153,3 +153,13 @@ csharp_using_directive_placement = outside_namespace:warning # File header (copyright notice) file_header_template = Copyright (c) 2025 TriasDev GmbH & Co. KG\nLicensed under the MIT License. See LICENSE file in the project root for full license information. + +# .NET diagnostic severity overrides +# IDE0011: Add braces +dotnet_diagnostic.IDE0011.severity = warning + +# IDE0040: Add accessibility modifiers +dotnet_diagnostic.IDE0040.severity = warning + +# IDE1006: Naming rule violations +dotnet_diagnostic.IDE1006.severity = warning diff --git a/TriasDev.Templify.Converter/Analyzers/TemplateAnalyzer.cs b/TriasDev.Templify.Converter/Analyzers/TemplateAnalyzer.cs index bf4f4b9..27a79bb 100644 --- a/TriasDev.Templify.Converter/Analyzers/TemplateAnalyzer.cs +++ b/TriasDev.Templify.Converter/Analyzers/TemplateAnalyzer.cs @@ -295,7 +295,10 @@ private string GetLocation(SdtElement sdt) { // Try to find the paragraph number Document? doc = sdt.Ancestors().FirstOrDefault(); - if (doc == null) return "Unknown"; + if (doc == null) + { + return "Unknown"; + } List allParagraphs = doc.Descendants().ToList(); Paragraph? paragraph = sdt.Ancestors().FirstOrDefault(); diff --git a/TriasDev.Templify.Converter/Converters/RepeatingConverter.cs b/TriasDev.Templify.Converter/Converters/RepeatingConverter.cs index 59eaa59..af132b8 100644 --- a/TriasDev.Templify.Converter/Converters/RepeatingConverter.cs +++ b/TriasDev.Templify.Converter/Converters/RepeatingConverter.cs @@ -42,7 +42,10 @@ public bool Convert(SdtElement sdt, string tag) foreach (SdtElement innerSdt in innerControls) { string? innerTag = OpenXmlHelpers.GetContentControlTag(innerSdt); - if (innerTag == null) continue; + if (innerTag == null) + { + continue; + } // Convert inner variables (relative to loop item) if (innerTag.StartsWith("variable_")) diff --git a/TriasDev.Templify.Demo/Program.cs b/TriasDev.Templify.Demo/Program.cs index 2f4196d..d71c1db 100644 --- a/TriasDev.Templify.Demo/Program.cs +++ b/TriasDev.Templify.Demo/Program.cs @@ -11,7 +11,7 @@ namespace TriasDev.Templify.Demo; internal class Program { - static void Main(string[] args) + private static void Main(string[] args) { Console.WriteLine("=== Templify Comprehensive Demo ==="); Console.WriteLine(); @@ -74,7 +74,7 @@ static void Main(string[] args) DemonstrateRealProcessTemplate(); } - static void CreateComprehensiveTemplate(string filePath) + private static void CreateComprehensiveTemplate(string filePath) { using WordprocessingDocument doc = WordprocessingDocument.Create(filePath, WordprocessingDocumentType.Document); MainDocumentPart mainPart = doc.AddMainDocumentPart(); @@ -303,7 +303,7 @@ static void CreateComprehensiveTemplate(string filePath) mainPart.Document.Save(); } - static Dictionary CreateComprehensiveTestData() + private static Dictionary CreateComprehensiveTestData() { return new Dictionary { @@ -447,7 +447,7 @@ static Dictionary CreateComprehensiveTestData() }; } - static ProcessingResult ProcessTemplate(string templatePath, string outputPath, Dictionary data) + private static ProcessingResult ProcessTemplate(string templatePath, string outputPath, Dictionary data) { try { @@ -470,7 +470,7 @@ static ProcessingResult ProcessTemplate(string templatePath, string outputPath, } } - static void DemonstrateJsonInput(string outputDir) + private static void DemonstrateJsonInput(string outputDir) { Console.WriteLine("=== JSON Input Demo ==="); Console.WriteLine(); @@ -564,7 +564,7 @@ static void DemonstrateJsonInput(string outputDir) } } - static void CreateSimpleJsonTemplate(string filePath) + private static void CreateSimpleJsonTemplate(string filePath) { using WordprocessingDocument doc = WordprocessingDocument.Create(filePath, WordprocessingDocumentType.Document); MainDocumentPart mainPart = doc.AddMainDocumentPart(); @@ -616,7 +616,7 @@ static void CreateSimpleJsonTemplate(string filePath) mainPart.Document.Save(); } - static void DemonstrateValidation(string outputDir) + private static void DemonstrateValidation(string outputDir) { Console.WriteLine("=== Template Validation Demo ==="); Console.WriteLine(); @@ -778,7 +778,7 @@ static void DemonstrateValidation(string outputDir) Console.WriteLine(" • Improve user experience"); } - static void DemonstrateRealProcessTemplate() + private static void DemonstrateRealProcessTemplate() { Console.WriteLine("=== Real Process Template Demo ==="); Console.WriteLine(); @@ -944,7 +944,7 @@ static void DemonstrateRealProcessTemplate() } } - static string? FindProjectDirectory(string startDir) + private static string? FindProjectDirectory(string startDir) { DirectoryInfo? current = new DirectoryInfo(startDir); @@ -962,7 +962,7 @@ static void DemonstrateRealProcessTemplate() return null; } - static void CreateValidTemplate(string filePath) + private static void CreateValidTemplate(string filePath) { using WordprocessingDocument doc = WordprocessingDocument.Create(filePath, WordprocessingDocumentType.Document); MainDocumentPart mainPart = doc.AddMainDocumentPart(); @@ -986,7 +986,7 @@ static void CreateValidTemplate(string filePath) mainPart.Document.Save(); } - static void CreateTemplateWithUnmatchedConditional(string filePath) + private static void CreateTemplateWithUnmatchedConditional(string filePath) { using WordprocessingDocument doc = WordprocessingDocument.Create(filePath, WordprocessingDocumentType.Document); MainDocumentPart mainPart = doc.AddMainDocumentPart(); @@ -1003,7 +1003,7 @@ static void CreateTemplateWithUnmatchedConditional(string filePath) mainPart.Document.Save(); } - static void CreateTemplateWithUnmatchedLoop(string filePath) + private static void CreateTemplateWithUnmatchedLoop(string filePath) { using WordprocessingDocument doc = WordprocessingDocument.Create(filePath, WordprocessingDocumentType.Document); MainDocumentPart mainPart = doc.AddMainDocumentPart(); @@ -1020,7 +1020,7 @@ static void CreateTemplateWithUnmatchedLoop(string filePath) mainPart.Document.Save(); } - static void CreateTemplateForDataValidation(string filePath) + private static void CreateTemplateForDataValidation(string filePath) { using WordprocessingDocument doc = WordprocessingDocument.Create(filePath, WordprocessingDocumentType.Document); MainDocumentPart mainPart = doc.AddMainDocumentPart(); @@ -1038,7 +1038,7 @@ static void CreateTemplateForDataValidation(string filePath) } // Helper methods for document creation - static void AddTitle(Body body, string text) + private static void AddTitle(Body body, string text) { Paragraph para = body.AppendChild(new Paragraph()); Run run = para.AppendChild(new Run()); @@ -1048,7 +1048,7 @@ static void AddTitle(Body body, string text) run.AppendChild(new Text(text)); } - static void AddHeading(Body body, string text) + private static void AddHeading(Body body, string text) { Paragraph para = body.AppendChild(new Paragraph()); Run run = para.AppendChild(new Run()); @@ -1058,26 +1058,33 @@ static void AddHeading(Body body, string text) run.AppendChild(new Text(text)); } - static void AddParagraph(Body body, string text) + private static void AddParagraph(Body body, string text) { Paragraph para = body.AppendChild(new Paragraph()); Run run = para.AppendChild(new Run()); run.AppendChild(new Text(text) { Space = SpaceProcessingModeValues.Preserve }); } - static void AddFormattedParagraph(Body body, string text, bool bold = false, bool italic = false) + private static void AddFormattedParagraph(Body body, string text, bool bold = false, bool italic = false) { Paragraph para = body.AppendChild(new Paragraph()); Run run = para.AppendChild(new Run()); RunProperties props = run.AppendChild(new RunProperties()); - if (bold) props.AppendChild(new Bold()); - if (italic) props.AppendChild(new Italic()); + if (bold) + { + props.AppendChild(new Bold()); + } + + if (italic) + { + props.AppendChild(new Italic()); + } run.AppendChild(new Text(text) { Space = SpaceProcessingModeValues.Preserve }); } - static void AddBulletListItem(Body body, string text, WordprocessingDocument document) + private static void AddBulletListItem(Body body, string text, WordprocessingDocument document) { EnsureNumberingPart(document); @@ -1095,7 +1102,7 @@ static void AddBulletListItem(Body body, string text, WordprocessingDocument doc run.AppendChild(new Text(text) { Space = SpaceProcessingModeValues.Preserve }); } - static void AddNumberedListItem(Body body, string text, WordprocessingDocument document) + private static void AddNumberedListItem(Body body, string text, WordprocessingDocument document) { EnsureNumberingPart(document); @@ -1113,7 +1120,7 @@ static void AddNumberedListItem(Body body, string text, WordprocessingDocument d run.AppendChild(new Text(text) { Space = SpaceProcessingModeValues.Preserve }); } - static void EnsureNumberingPart(WordprocessingDocument document) + private static void EnsureNumberingPart(WordprocessingDocument document) { MainDocumentPart mainPart = document.MainDocumentPart!; @@ -1160,7 +1167,7 @@ static void EnsureNumberingPart(WordprocessingDocument document) } } - static Table CreateTable(Body body, int rows, int cols) + private static Table CreateTable(Body body, int rows, int cols) { Table table = new Table(); @@ -1193,7 +1200,7 @@ static Table CreateTable(Body body, int rows, int cols) return table; } - static void SetCellText(Table table, int row, int col, string text) + private static void SetCellText(Table table, int row, int col, string text) { TableRow? tr = table.Elements().ElementAtOrDefault(row); TableCell? cell = tr?.Elements().ElementAtOrDefault(col); diff --git a/TriasDev.Templify.DocumentGenerator/StirlingPdfConverter.cs b/TriasDev.Templify.DocumentGenerator/StirlingPdfConverter.cs index 9ee4353..8c41a4a 100644 --- a/TriasDev.Templify.DocumentGenerator/StirlingPdfConverter.cs +++ b/TriasDev.Templify.DocumentGenerator/StirlingPdfConverter.cs @@ -102,7 +102,10 @@ private async Task ConvertPdfToPngAsync(byte[] pdfBytes) /// private static bool IsZipFile(byte[] bytes) { - if (bytes.Length < 4) return false; + if (bytes.Length < 4) + { + return false; + } // ZIP file magic number: 50 4B 03 04 or 50 4B 05 06 return bytes[0] == 0x50 && bytes[1] == 0x4B && (bytes[2] == 0x03 || bytes[2] == 0x05); diff --git a/TriasDev.Templify.Gui/App.axaml.cs b/TriasDev.Templify.Gui/App.axaml.cs index 82fdd69..9ff3835 100644 --- a/TriasDev.Templify.Gui/App.axaml.cs +++ b/TriasDev.Templify.Gui/App.axaml.cs @@ -1,89 +1,89 @@ // Copyright (c) 2025 TriasDev GmbH & Co. KG // Licensed under the MIT License. See LICENSE file in the project root for full license information. -using System; -using System.Linq; -using Avalonia; -using Avalonia.Controls.ApplicationLifetimes; -using Avalonia.Data.Core; -using Avalonia.Data.Core.Plugins; -using Avalonia.Markup.Xaml; -using Microsoft.Extensions.DependencyInjection; -using TriasDev.Templify.Gui.Services; -using TriasDev.Templify.Gui.ViewModels; -using TriasDev.Templify.Gui.Views; - -namespace TriasDev.Templify.Gui; - -public partial class App : Application -{ - public IServiceProvider? Services { get; private set; } - - public override void Initialize() - { - AvaloniaXamlLoader.Load(this); - } - - public override void OnFrameworkInitializationCompleted() - { - if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) - { - // Avoid duplicate validations from both Avalonia and the CommunityToolkit. - // More info: https://docs.avaloniaui.net/docs/guides/development-guides/data-validation#manage-validationplugins - DisableAvaloniaDataAnnotationValidation(); - - // Create MainWindow first - MainWindow mainWindow = new MainWindow(); - desktop.MainWindow = mainWindow; - - // Configure dependency injection after window is created - ServiceCollection services = new ServiceCollection(); - ConfigureServices(services); - Services = services.BuildServiceProvider(); - - // Create ViewModel with DI - MainWindowViewModel viewModel = Services.GetRequiredService(); - mainWindow.DataContext = viewModel; - } - - base.OnFrameworkInitializationCompleted(); - } - - private void ConfigureServices(IServiceCollection services) - { - // Core services - services.AddSingleton(); - - // ViewModels - services.AddTransient(); - - // FileDialogService needs IStorageProvider from the window - // We'll register it as a factory that gets the StorageProvider from the MainWindow - services.AddTransient(provider => - { - if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) - { - MainWindow? mainWindow = desktop.MainWindow as MainWindow; - if (mainWindow?.StorageProvider != null) - { - return new FileDialogService(mainWindow.StorageProvider); - } - } - - throw new InvalidOperationException("MainWindow not available for FileDialogService"); - }); - } - - private void DisableAvaloniaDataAnnotationValidation() - { - // Get an array of plugins to remove - DataAnnotationsValidationPlugin[] dataValidationPluginsToRemove = - BindingPlugins.DataValidators.OfType().ToArray(); - - // remove each entry found - foreach (DataAnnotationsValidationPlugin plugin in dataValidationPluginsToRemove) - { - BindingPlugins.DataValidators.Remove(plugin); - } - } -} +using System; +using System.Linq; +using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Data.Core; +using Avalonia.Data.Core.Plugins; +using Avalonia.Markup.Xaml; +using Microsoft.Extensions.DependencyInjection; +using TriasDev.Templify.Gui.Services; +using TriasDev.Templify.Gui.ViewModels; +using TriasDev.Templify.Gui.Views; + +namespace TriasDev.Templify.Gui; + +public partial class App : Application +{ + public IServiceProvider? Services { get; private set; } + + public override void Initialize() + { + AvaloniaXamlLoader.Load(this); + } + + public override void OnFrameworkInitializationCompleted() + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + // Avoid duplicate validations from both Avalonia and the CommunityToolkit. + // More info: https://docs.avaloniaui.net/docs/guides/development-guides/data-validation#manage-validationplugins + DisableAvaloniaDataAnnotationValidation(); + + // Create MainWindow first + MainWindow mainWindow = new MainWindow(); + desktop.MainWindow = mainWindow; + + // Configure dependency injection after window is created + ServiceCollection services = new ServiceCollection(); + ConfigureServices(services); + Services = services.BuildServiceProvider(); + + // Create ViewModel with DI + MainWindowViewModel viewModel = Services.GetRequiredService(); + mainWindow.DataContext = viewModel; + } + + base.OnFrameworkInitializationCompleted(); + } + + private void ConfigureServices(IServiceCollection services) + { + // Core services + services.AddSingleton(); + + // ViewModels + services.AddTransient(); + + // FileDialogService needs IStorageProvider from the window + // We'll register it as a factory that gets the StorageProvider from the MainWindow + services.AddTransient(provider => + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + MainWindow? mainWindow = desktop.MainWindow as MainWindow; + if (mainWindow?.StorageProvider != null) + { + return new FileDialogService(mainWindow.StorageProvider); + } + } + + throw new InvalidOperationException("MainWindow not available for FileDialogService"); + }); + } + + private void DisableAvaloniaDataAnnotationValidation() + { + // Get an array of plugins to remove + DataAnnotationsValidationPlugin[] dataValidationPluginsToRemove = + BindingPlugins.DataValidators.OfType().ToArray(); + + // remove each entry found + foreach (DataAnnotationsValidationPlugin plugin in dataValidationPluginsToRemove) + { + BindingPlugins.DataValidators.Remove(plugin); + } + } +} diff --git a/TriasDev.Templify.Gui/Program.cs b/TriasDev.Templify.Gui/Program.cs index aeba6aa..ee732b7 100644 --- a/TriasDev.Templify.Gui/Program.cs +++ b/TriasDev.Templify.Gui/Program.cs @@ -1,24 +1,24 @@ // Copyright (c) 2025 TriasDev GmbH & Co. KG // Licensed under the MIT License. See LICENSE file in the project root for full license information. -using Avalonia; -using System; - -namespace TriasDev.Templify.Gui; - -sealed class Program -{ - // Initialization code. Don't use any Avalonia, third-party APIs or any - // SynchronizationContext-reliant code before AppMain is called: things aren't initialized - // yet and stuff might break. - [STAThread] - public static void Main(string[] args) => BuildAvaloniaApp() - .StartWithClassicDesktopLifetime(args); - - // Avalonia configuration, don't remove; also used by visual designer. - public static AppBuilder BuildAvaloniaApp() - => AppBuilder.Configure() - .UsePlatformDetect() - .WithInterFont() - .LogToTrace(); -} +using Avalonia; +using System; + +namespace TriasDev.Templify.Gui; + +internal sealed class Program +{ + // Initialization code. Don't use any Avalonia, third-party APIs or any + // SynchronizationContext-reliant code before AppMain is called: things aren't initialized + // yet and stuff might break. + [STAThread] + public static void Main(string[] args) => BuildAvaloniaApp() + .StartWithClassicDesktopLifetime(args); + + // Avalonia configuration, don't remove; also used by visual designer. + public static AppBuilder BuildAvaloniaApp() + => AppBuilder.Configure() + .UsePlatformDetect() + .WithInterFont() + .LogToTrace(); +} diff --git a/TriasDev.Templify.Gui/TriasDev.Templify.Gui.csproj b/TriasDev.Templify.Gui/TriasDev.Templify.Gui.csproj index 3439879..9c17d29 100644 --- a/TriasDev.Templify.Gui/TriasDev.Templify.Gui.csproj +++ b/TriasDev.Templify.Gui/TriasDev.Templify.Gui.csproj @@ -1,33 +1,33 @@ - - - WinExe - net9.0 - enable - true - app.manifest - true - - - - - - - - - - - - - - - None - All - - - - - - - - - + + + WinExe + net9.0 + enable + true + app.manifest + true + + + + + + + + + + + + + + + None + All + + + + + + + + + diff --git a/TriasDev.Templify.Gui/ViewLocator.cs b/TriasDev.Templify.Gui/ViewLocator.cs index bed89e0..10d1643 100644 --- a/TriasDev.Templify.Gui/ViewLocator.cs +++ b/TriasDev.Templify.Gui/ViewLocator.cs @@ -1,40 +1,42 @@ // Copyright (c) 2025 TriasDev GmbH & Co. KG // Licensed under the MIT License. See LICENSE file in the project root for full license information. -using System; -using System.Diagnostics.CodeAnalysis; -using Avalonia.Controls; -using Avalonia.Controls.Templates; -using TriasDev.Templify.Gui.ViewModels; - -namespace TriasDev.Templify.Gui; - -/// -/// Given a view model, returns the corresponding view if possible. -/// -[RequiresUnreferencedCode( - "Default implementation of ViewLocator involves reflection which may be trimmed away.", - Url = "https://docs.avaloniaui.net/docs/concepts/view-locator")] -public class ViewLocator : IDataTemplate -{ - public Control? Build(object? param) - { - if (param is null) +using System; +using System.Diagnostics.CodeAnalysis; +using Avalonia.Controls; +using Avalonia.Controls.Templates; +using TriasDev.Templify.Gui.ViewModels; + +namespace TriasDev.Templify.Gui; + +/// +/// Given a view model, returns the corresponding view if possible. +/// +[RequiresUnreferencedCode( + "Default implementation of ViewLocator involves reflection which may be trimmed away.", + Url = "https://docs.avaloniaui.net/docs/concepts/view-locator")] +public class ViewLocator : IDataTemplate +{ + public Control? Build(object? param) + { + if (param is null) + { return null; + } - var name = param.GetType().FullName!.Replace("ViewModel", "View", StringComparison.Ordinal); - var type = Type.GetType(name); - - if (type != null) - { - return (Control)Activator.CreateInstance(type)!; + var name = param.GetType().FullName!.Replace("ViewModel", "View", StringComparison.Ordinal); + var type = Type.GetType(name); + + if (type != null) + { + return (Control)Activator.CreateInstance(type)!; } - return new TextBlock { Text = "Not Found: " + name }; - } - - public bool Match(object? data) - { - return data is ViewModelBase; - } -} + return new TextBlock { Text = "Not Found: " + name }; + } + + public bool Match(object? data) + { + return data is ViewModelBase; + } +} diff --git a/TriasDev.Templify.Gui/ViewModels/MainWindowViewModel.cs b/TriasDev.Templify.Gui/ViewModels/MainWindowViewModel.cs index b9a7d67..5a5a027 100644 --- a/TriasDev.Templify.Gui/ViewModels/MainWindowViewModel.cs +++ b/TriasDev.Templify.Gui/ViewModels/MainWindowViewModel.cs @@ -1,276 +1,284 @@ // Copyright (c) 2025 TriasDev GmbH & Co. KG // Licensed under the MIT License. See LICENSE file in the project root for full license information. -using System; -using System.Collections.ObjectModel; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Input; -using TriasDev.Templify.Core; -using TriasDev.Templify.Gui.Models; -using TriasDev.Templify.Gui.Services; - -namespace TriasDev.Templify.Gui.ViewModels; - -public partial class MainWindowViewModel : ViewModelBase -{ - private readonly ITemplifyService _templifyService; - private readonly IFileDialogService _fileDialogService; - - [ObservableProperty] - [NotifyCanExecuteChangedFor(nameof(ValidateTemplateCommand))] - [NotifyCanExecuteChangedFor(nameof(ProcessTemplateCommand))] - private string? _templatePath; - - [ObservableProperty] - [NotifyCanExecuteChangedFor(nameof(ProcessTemplateCommand))] - private string? _jsonPath; - - [ObservableProperty] - [NotifyCanExecuteChangedFor(nameof(ProcessTemplateCommand))] - [NotifyCanExecuteChangedFor(nameof(OpenOutputFileCommand))] - private string? _outputPath; - - [ObservableProperty] - [NotifyCanExecuteChangedFor(nameof(ValidateTemplateCommand))] - [NotifyCanExecuteChangedFor(nameof(ProcessTemplateCommand))] - private bool _isProcessing; - - [ObservableProperty] - private double _progress; - - [ObservableProperty] - private string _statusMessage = "Ready"; - - [ObservableProperty] - private ObservableCollection _results = new(); - - public MainWindowViewModel( - ITemplifyService templifyService, - IFileDialogService fileDialogService) - { - _templifyService = templifyService; - _fileDialogService = fileDialogService; - } - - [RelayCommand] - private async Task BrowseTemplateAsync() - { - string? selectedFile = await _fileDialogService.OpenTemplateFileAsync(); - if (selectedFile != null) - { - TemplatePath = selectedFile; - UpdateOutputPath(); - } - } - - [RelayCommand] - private async Task BrowseJsonAsync() - { - string? selectedFile = await _fileDialogService.OpenJsonFileAsync(); - if (selectedFile != null) - { - JsonPath = selectedFile; - UpdateOutputPath(); - } - } - - [RelayCommand] - private async Task BrowseOutputAsync() - { - string defaultName = GenerateOutputFileName(); - string? selectedFile = await _fileDialogService.SaveOutputFileAsync(defaultName); - if (selectedFile != null) - { - OutputPath = selectedFile; - } - } - - [RelayCommand(CanExecute = nameof(CanValidate))] - private async Task ValidateTemplateAsync() - { - if (string.IsNullOrEmpty(TemplatePath)) - return; - - IsProcessing = true; - StatusMessage = "Validating template..."; - Results.Clear(); - - try - { - ValidationResult validation = await _templifyService.ValidateTemplateAsync(TemplatePath, JsonPath); - - if (validation.IsValid) - { - Results.Add("✓ Template is valid!"); - Results.Add($"✓ Found {validation.AllPlaceholders.Count} placeholders"); - - if (validation.AllPlaceholders.Count > 0) - { - Results.Add($" Placeholders: {string.Join(", ", validation.AllPlaceholders.Take(10))}"); - if (validation.AllPlaceholders.Count > 10) - { - Results.Add($" ... and {validation.AllPlaceholders.Count - 10} more"); - } - } - } - else - { - Results.Add("✗ Template has validation errors:"); - foreach (ValidationError error in validation.Errors) - { - Results.Add($" - {error.Type}: {error.Message}"); - } - } - - if (!string.IsNullOrEmpty(JsonPath) && validation.MissingVariables.Count > 0) - { - Results.Add($"⚠ {validation.MissingVariables.Count} missing variables:"); - foreach (string missing in validation.MissingVariables.Take(5)) - { - Results.Add($" - {missing}"); - } - if (validation.MissingVariables.Count > 5) - { - Results.Add($" ... and {validation.MissingVariables.Count - 5} more"); - } - } - - StatusMessage = validation.IsValid ? "Validation successful" : "Validation failed"; - } - catch (Exception ex) - { - Results.Add($"✗ Error during validation: {ex.Message}"); - StatusMessage = "Validation failed"; - } - finally - { - IsProcessing = false; - } - } - - private bool CanValidate() => !string.IsNullOrEmpty(TemplatePath) && !IsProcessing; - - [RelayCommand(CanExecute = nameof(CanProcess))] - private async Task ProcessTemplateAsync() - { - if (string.IsNullOrEmpty(TemplatePath) || string.IsNullOrEmpty(JsonPath) || string.IsNullOrEmpty(OutputPath)) - return; - - IsProcessing = true; - StatusMessage = "Processing template..."; - Results.Clear(); - Progress = 0; - - try - { - Progress progressReporter = new Progress(p => Progress = p); - - UiProcessingResult result = await _templifyService.ProcessTemplateAsync( - TemplatePath, - JsonPath, - OutputPath, - progressReporter); - - if (result.Success) - { - Results.Add("✓ Template processed successfully!"); - Results.Add($"✓ Made {result.Processing.ReplacementCount} replacements"); - Results.Add($"✓ Output saved to: {result.OutputPath}"); - - if (result.Validation.MissingVariables.Count > 0) - { - Results.Add($"⚠ {result.Validation.MissingVariables.Count} missing variables:"); - foreach (string missing in result.Validation.MissingVariables.Take(5)) - { - Results.Add($" - {missing}"); - } - if (result.Validation.MissingVariables.Count > 5) - { - Results.Add($" ... and {result.Validation.MissingVariables.Count - 5} more"); - } - } - - StatusMessage = "Processing complete"; - } - else - { - Results.Add($"✗ Processing failed: {result.Processing.ErrorMessage}"); - StatusMessage = "Processing failed"; - } - } - catch (Exception ex) - { - Results.Add($"✗ Error during processing: {ex.Message}"); - StatusMessage = "Processing failed"; - } - finally - { - IsProcessing = false; - Progress = 0; - } - } - - private bool CanProcess() => - !string.IsNullOrEmpty(TemplatePath) && - !string.IsNullOrEmpty(JsonPath) && - !string.IsNullOrEmpty(OutputPath) && - !IsProcessing; - - [RelayCommand(CanExecute = nameof(CanOpenOutput))] - private void OpenOutputFile() - { - if (string.IsNullOrEmpty(OutputPath) || !File.Exists(OutputPath)) - return; - - try - { - Process.Start(new ProcessStartInfo - { - FileName = OutputPath, - UseShellExecute = true - }); - } - catch (Exception ex) - { - Results.Add($"✗ Failed to open output file: {ex.Message}"); - } - } - - private bool CanOpenOutput() => !string.IsNullOrEmpty(OutputPath) && File.Exists(OutputPath); - - [RelayCommand] - private void Clear() - { - TemplatePath = null; - JsonPath = null; - OutputPath = null; - Results.Clear(); - StatusMessage = "Ready"; - Progress = 0; - } - - private void UpdateOutputPath() - { - if (string.IsNullOrEmpty(TemplatePath)) - return; - - string dir = Path.GetDirectoryName(TemplatePath) ?? "."; - string filename = GenerateOutputFileName(); - OutputPath = Path.Combine(dir, filename); - } - - private string GenerateOutputFileName() - { - if (!string.IsNullOrEmpty(TemplatePath)) - { - string templateName = Path.GetFileNameWithoutExtension(TemplatePath); - return $"{templateName}-output.docx"; - } - - return "output.docx"; - } -} +using System; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using TriasDev.Templify.Core; +using TriasDev.Templify.Gui.Models; +using TriasDev.Templify.Gui.Services; + +namespace TriasDev.Templify.Gui.ViewModels; + +public partial class MainWindowViewModel : ViewModelBase +{ + private readonly ITemplifyService _templifyService; + private readonly IFileDialogService _fileDialogService; + + [ObservableProperty] + [NotifyCanExecuteChangedFor(nameof(ValidateTemplateCommand))] + [NotifyCanExecuteChangedFor(nameof(ProcessTemplateCommand))] + private string? _templatePath; + + [ObservableProperty] + [NotifyCanExecuteChangedFor(nameof(ProcessTemplateCommand))] + private string? _jsonPath; + + [ObservableProperty] + [NotifyCanExecuteChangedFor(nameof(ProcessTemplateCommand))] + [NotifyCanExecuteChangedFor(nameof(OpenOutputFileCommand))] + private string? _outputPath; + + [ObservableProperty] + [NotifyCanExecuteChangedFor(nameof(ValidateTemplateCommand))] + [NotifyCanExecuteChangedFor(nameof(ProcessTemplateCommand))] + private bool _isProcessing; + + [ObservableProperty] + private double _progress; + + [ObservableProperty] + private string _statusMessage = "Ready"; + + [ObservableProperty] + private ObservableCollection _results = new(); + + public MainWindowViewModel( + ITemplifyService templifyService, + IFileDialogService fileDialogService) + { + _templifyService = templifyService; + _fileDialogService = fileDialogService; + } + + [RelayCommand] + private async Task BrowseTemplateAsync() + { + string? selectedFile = await _fileDialogService.OpenTemplateFileAsync(); + if (selectedFile != null) + { + TemplatePath = selectedFile; + UpdateOutputPath(); + } + } + + [RelayCommand] + private async Task BrowseJsonAsync() + { + string? selectedFile = await _fileDialogService.OpenJsonFileAsync(); + if (selectedFile != null) + { + JsonPath = selectedFile; + UpdateOutputPath(); + } + } + + [RelayCommand] + private async Task BrowseOutputAsync() + { + string defaultName = GenerateOutputFileName(); + string? selectedFile = await _fileDialogService.SaveOutputFileAsync(defaultName); + if (selectedFile != null) + { + OutputPath = selectedFile; + } + } + + [RelayCommand(CanExecute = nameof(CanValidate))] + private async Task ValidateTemplateAsync() + { + if (string.IsNullOrEmpty(TemplatePath)) + { + return; + } + + IsProcessing = true; + StatusMessage = "Validating template..."; + Results.Clear(); + + try + { + ValidationResult validation = await _templifyService.ValidateTemplateAsync(TemplatePath, JsonPath); + + if (validation.IsValid) + { + Results.Add("✓ Template is valid!"); + Results.Add($"✓ Found {validation.AllPlaceholders.Count} placeholders"); + + if (validation.AllPlaceholders.Count > 0) + { + Results.Add($" Placeholders: {string.Join(", ", validation.AllPlaceholders.Take(10))}"); + if (validation.AllPlaceholders.Count > 10) + { + Results.Add($" ... and {validation.AllPlaceholders.Count - 10} more"); + } + } + } + else + { + Results.Add("✗ Template has validation errors:"); + foreach (ValidationError error in validation.Errors) + { + Results.Add($" - {error.Type}: {error.Message}"); + } + } + + if (!string.IsNullOrEmpty(JsonPath) && validation.MissingVariables.Count > 0) + { + Results.Add($"⚠ {validation.MissingVariables.Count} missing variables:"); + foreach (string missing in validation.MissingVariables.Take(5)) + { + Results.Add($" - {missing}"); + } + if (validation.MissingVariables.Count > 5) + { + Results.Add($" ... and {validation.MissingVariables.Count - 5} more"); + } + } + + StatusMessage = validation.IsValid ? "Validation successful" : "Validation failed"; + } + catch (Exception ex) + { + Results.Add($"✗ Error during validation: {ex.Message}"); + StatusMessage = "Validation failed"; + } + finally + { + IsProcessing = false; + } + } + + private bool CanValidate() => !string.IsNullOrEmpty(TemplatePath) && !IsProcessing; + + [RelayCommand(CanExecute = nameof(CanProcess))] + private async Task ProcessTemplateAsync() + { + if (string.IsNullOrEmpty(TemplatePath) || string.IsNullOrEmpty(JsonPath) || string.IsNullOrEmpty(OutputPath)) + { + return; + } + + IsProcessing = true; + StatusMessage = "Processing template..."; + Results.Clear(); + Progress = 0; + + try + { + Progress progressReporter = new Progress(p => Progress = p); + + UiProcessingResult result = await _templifyService.ProcessTemplateAsync( + TemplatePath, + JsonPath, + OutputPath, + progressReporter); + + if (result.Success) + { + Results.Add("✓ Template processed successfully!"); + Results.Add($"✓ Made {result.Processing.ReplacementCount} replacements"); + Results.Add($"✓ Output saved to: {result.OutputPath}"); + + if (result.Validation.MissingVariables.Count > 0) + { + Results.Add($"⚠ {result.Validation.MissingVariables.Count} missing variables:"); + foreach (string missing in result.Validation.MissingVariables.Take(5)) + { + Results.Add($" - {missing}"); + } + if (result.Validation.MissingVariables.Count > 5) + { + Results.Add($" ... and {result.Validation.MissingVariables.Count - 5} more"); + } + } + + StatusMessage = "Processing complete"; + } + else + { + Results.Add($"✗ Processing failed: {result.Processing.ErrorMessage}"); + StatusMessage = "Processing failed"; + } + } + catch (Exception ex) + { + Results.Add($"✗ Error during processing: {ex.Message}"); + StatusMessage = "Processing failed"; + } + finally + { + IsProcessing = false; + Progress = 0; + } + } + + private bool CanProcess() => + !string.IsNullOrEmpty(TemplatePath) && + !string.IsNullOrEmpty(JsonPath) && + !string.IsNullOrEmpty(OutputPath) && + !IsProcessing; + + [RelayCommand(CanExecute = nameof(CanOpenOutput))] + private void OpenOutputFile() + { + if (string.IsNullOrEmpty(OutputPath) || !File.Exists(OutputPath)) + { + return; + } + + try + { + Process.Start(new ProcessStartInfo + { + FileName = OutputPath, + UseShellExecute = true + }); + } + catch (Exception ex) + { + Results.Add($"✗ Failed to open output file: {ex.Message}"); + } + } + + private bool CanOpenOutput() => !string.IsNullOrEmpty(OutputPath) && File.Exists(OutputPath); + + [RelayCommand] + private void Clear() + { + TemplatePath = null; + JsonPath = null; + OutputPath = null; + Results.Clear(); + StatusMessage = "Ready"; + Progress = 0; + } + + private void UpdateOutputPath() + { + if (string.IsNullOrEmpty(TemplatePath)) + { + return; + } + + string dir = Path.GetDirectoryName(TemplatePath) ?? "."; + string filename = GenerateOutputFileName(); + OutputPath = Path.Combine(dir, filename); + } + + private string GenerateOutputFileName() + { + if (!string.IsNullOrEmpty(TemplatePath)) + { + string templateName = Path.GetFileNameWithoutExtension(TemplatePath); + return $"{templateName}-output.docx"; + } + + return "output.docx"; + } +} diff --git a/TriasDev.Templify.Gui/ViewModels/ViewModelBase.cs b/TriasDev.Templify.Gui/ViewModels/ViewModelBase.cs index e8a77cb..5abe92a 100644 --- a/TriasDev.Templify.Gui/ViewModels/ViewModelBase.cs +++ b/TriasDev.Templify.Gui/ViewModels/ViewModelBase.cs @@ -1,10 +1,10 @@ // Copyright (c) 2025 TriasDev GmbH & Co. KG // Licensed under the MIT License. See LICENSE file in the project root for full license information. -using CommunityToolkit.Mvvm.ComponentModel; - -namespace TriasDev.Templify.Gui.ViewModels; - -public abstract class ViewModelBase : ObservableObject -{ -} +using CommunityToolkit.Mvvm.ComponentModel; + +namespace TriasDev.Templify.Gui.ViewModels; + +public abstract class ViewModelBase : ObservableObject +{ +} diff --git a/TriasDev.Templify.Gui/Views/MainWindow.axaml.cs b/TriasDev.Templify.Gui/Views/MainWindow.axaml.cs index 484bfe9..bb24634 100644 --- a/TriasDev.Templify.Gui/Views/MainWindow.axaml.cs +++ b/TriasDev.Templify.Gui/Views/MainWindow.axaml.cs @@ -1,14 +1,14 @@ // Copyright (c) 2025 TriasDev GmbH & Co. KG // Licensed under the MIT License. See LICENSE file in the project root for full license information. -using Avalonia.Controls; - -namespace TriasDev.Templify.Gui.Views; - -public partial class MainWindow : Window -{ - public MainWindow() - { - InitializeComponent(); - } -} \ No newline at end of file +using Avalonia.Controls; + +namespace TriasDev.Templify.Gui.Views; + +public partial class MainWindow : Window +{ + public MainWindow() + { + InitializeComponent(); + } +} diff --git a/TriasDev.Templify.Tests/Visitors/DocumentWalkerTests.cs b/TriasDev.Templify.Tests/Visitors/DocumentWalkerTests.cs index c6355f6..f4f6be5 100644 --- a/TriasDev.Templify.Tests/Visitors/DocumentWalkerTests.cs +++ b/TriasDev.Templify.Tests/Visitors/DocumentWalkerTests.cs @@ -360,9 +360,20 @@ public void VisitConditional(ConditionalBlock conditional, IEvaluationContext co } // Remove markers - if (conditional.StartMarker.Parent != null) conditional.StartMarker.Remove(); - if (conditional.ElseMarker?.Parent != null) conditional.ElseMarker.Remove(); - if (conditional.EndMarker.Parent != null) conditional.EndMarker.Remove(); + if (conditional.StartMarker.Parent != null) + { + conditional.StartMarker.Remove(); + } + + if (conditional.ElseMarker?.Parent != null) + { + conditional.ElseMarker.Remove(); + } + + if (conditional.EndMarker.Parent != null) + { + conditional.EndMarker.Remove(); + } } public void VisitLoop(LoopBlock loop, IEvaluationContext context) diff --git a/TriasDev.Templify/Conditionals/ConditionalEvaluator.cs b/TriasDev.Templify/Conditionals/ConditionalEvaluator.cs index e6c0839..889577c 100644 --- a/TriasDev.Templify/Conditionals/ConditionalEvaluator.cs +++ b/TriasDev.Templify/Conditionals/ConditionalEvaluator.cs @@ -392,8 +392,15 @@ private bool EvaluateValue(object? value) /// private bool AreEqual(object? left, object? right) { - if (left == null && right == null) return true; - if (left == null || right == null) return false; + if (left == null && right == null) + { + return true; + } + + if (left == null || right == null) + { + return false; + } return left.ToString() == right.ToString(); } diff --git a/TriasDev.Templify/Expressions/BooleanExpression.cs b/TriasDev.Templify/Expressions/BooleanExpression.cs index 647e045..91d7d8f 100644 --- a/TriasDev.Templify/Expressions/BooleanExpression.cs +++ b/TriasDev.Templify/Expressions/BooleanExpression.cs @@ -127,9 +127,20 @@ public override bool Evaluate(IDataContext context) private static int Compare(object? left, object? right) { - if (left == null && right == null) return 0; - if (left == null) return -1; - if (right == null) return 1; + if (left == null && right == null) + { + return 0; + } + + if (left == null) + { + return -1; + } + + if (right == null) + { + return 1; + } if (left is IComparable leftComparable && right is IComparable) { diff --git a/TriasDev.Templify/Expressions/BooleanExpressionParser.cs b/TriasDev.Templify/Expressions/BooleanExpressionParser.cs index e9874f7..94e6141 100644 --- a/TriasDev.Templify/Expressions/BooleanExpressionParser.cs +++ b/TriasDev.Templify/Expressions/BooleanExpressionParser.cs @@ -62,12 +62,19 @@ internal sealed class BooleanExpressionParser private BooleanExpression? ParseOrExpression() { BooleanExpression? left = ParseAndExpression(); - if (left == null) return null; + if (left == null) + { + return null; + } while (ConsumeKeyword("or")) { BooleanExpression? right = ParseAndExpression(); - if (right == null) return null; + if (right == null) + { + return null; + } + left = new OrExpression(left, right); } @@ -77,12 +84,19 @@ internal sealed class BooleanExpressionParser private BooleanExpression? ParseAndExpression() { BooleanExpression? left = ParseUnaryExpression(); - if (left == null) return null; + if (left == null) + { + return null; + } while (ConsumeKeyword("and")) { BooleanExpression? right = ParseUnaryExpression(); - if (right == null) return null; + if (right == null) + { + return null; + } + left = new AndExpression(left, right); } @@ -120,7 +134,10 @@ internal sealed class BooleanExpressionParser // Try to parse comparison string? identifier = ParseIdentifier(); - if (identifier == null) return null; + if (identifier == null) + { + return null; + } SkipWhitespace(); From 4adeaee219eac85d9c84758a14db371ad9b966dd Mon Sep 17 00:00:00 2001 From: Vaceslav Ustinov Date: Fri, 21 Nov 2025 23:04:14 +0100 Subject: [PATCH 2/3] docs: split documentation into template author and developer sections Split documentation into two clear audiences to improve accessibility: - Template Authors: Non-technical users creating Word templates (JSON only, no C# code) - Developers: Technical users integrating the library (C# API, examples, architecture) ## Changes ### New Template Author Documentation (docs/for-template-authors/) - getting-started.md - 5-minute intro with JSON examples - json-basics.md - Beginner-friendly JSON tutorial - template-syntax.md - Complete syntax reference - placeholders.md - Placeholder usage guide - conditionals.md - If/else logic guide - loops.md - Foreach loops guide - best-practices.md - Tips and troubleshooting - examples-gallery.md - Visual examples gallery - Moved format-specifiers.md from guides/ - Moved boolean-expressions.md from guides/ ### New Developer Documentation (docs/for-developers/) - quick-start.md - Placeholder linking to existing dev docs ### Updated Landing Page (docs/index.md) - Added audience selector at top - Clear paths: "I Create Word Templates" vs "I'm a Developer" - Removed C# code from main example (switched to JSON) - Simplified structure for better UX ### Updated Navigation (mkdocs.yml) - New "For Template Authors" section with all guides - New "For Developers" section (placeholder) - Kept existing Tutorials and FAQ - Marked old Quick Start as "Legacy" ### Fixed Broken Links - docs/FAQ.md: 2 links updated to new paths - docs/quick-start.md: 2 links updated to new paths - docs/tutorials/index.md: 2 links updated to new paths ### Additional Files - examples/README.md - Instructions for downloadable examples ## Impact - Template authors never see C# code or Dictionary - All template author examples use JSON only - Beginner-friendly JSON basics explained from scratch - Clear separation of concerns for different user types ## Related Remaining cleanup tracked in #20 --- docs/FAQ.md | 4 +- docs/for-developers/quick-start.md | 57 ++ docs/for-template-authors/best-practices.md | 618 ++++++++++++ .../boolean-expressions.md | 0 docs/for-template-authors/conditionals.md | 880 +++++++++++++++++ docs/for-template-authors/examples-gallery.md | 146 +++ .../format-specifiers.md | 0 docs/for-template-authors/getting-started.md | 248 +++++ docs/for-template-authors/json-basics.md | 485 ++++++++++ docs/for-template-authors/loops.md | 898 ++++++++++++++++++ docs/for-template-authors/placeholders.md | 770 +++++++++++++++ docs/for-template-authors/template-syntax.md | 660 +++++++++++++ docs/index.md | 141 +-- docs/quick-start.md | 4 +- docs/tutorials/index.md | 4 +- examples/README.md | 121 +++ mkdocs.yml | 19 +- 17 files changed, 4989 insertions(+), 66 deletions(-) create mode 100644 docs/for-developers/quick-start.md create mode 100644 docs/for-template-authors/best-practices.md rename docs/{guides => for-template-authors}/boolean-expressions.md (100%) create mode 100644 docs/for-template-authors/conditionals.md create mode 100644 docs/for-template-authors/examples-gallery.md rename docs/{guides => for-template-authors}/format-specifiers.md (100%) create mode 100644 docs/for-template-authors/getting-started.md create mode 100644 docs/for-template-authors/json-basics.md create mode 100644 docs/for-template-authors/loops.md create mode 100644 docs/for-template-authors/placeholders.md create mode 100644 docs/for-template-authors/template-syntax.md create mode 100644 examples/README.md diff --git a/docs/FAQ.md b/docs/FAQ.md index 9d3d74c..eaae030 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -331,7 +331,7 @@ Valid: ✓ - `enabled` → Enabled/Disabled - `active` → Active/Inactive -See the [Format Specifiers Guide](guides/format-specifiers.md) for complete documentation. +See the [Format Specifiers Guide](for-template-authors/format-specifiers.md) for complete documentation. ### Q: Can I use JSON instead of C# dictionaries? @@ -402,7 +402,7 @@ Can proceed: ✓ - Comparison: `==`, `!=`, `>`, `>=`, `<`, `<=` - Nested: `((var1 or var2) and var3)` -See the [Boolean Expressions Guide](guides/boolean-expressions.md) for complete documentation. +See the [Boolean Expressions Guide](for-template-authors/boolean-expressions.md) for complete documentation. ### Q: Can I combine expressions with format specifiers? diff --git a/docs/for-developers/quick-start.md b/docs/for-developers/quick-start.md new file mode 100644 index 0000000..4047ded --- /dev/null +++ b/docs/for-developers/quick-start.md @@ -0,0 +1,57 @@ +# Developer Documentation - Coming Soon! + +We're currently reorganizing our documentation to better serve both template authors and developers. + +## What's Coming + +The developer documentation section will include: + +- **Installation Guide** - NuGet package installation and setup +- **Quick Start Guide** - Your first Templify integration in C# +- **API Reference** - Complete API documentation with examples +- **Code Examples** - Real-world integration patterns +- **Architecture Overview** - Understanding the visitor pattern and internals +- **Performance Guide** - Optimization tips and benchmarks + +## In the Meantime + +You can find comprehensive developer documentation in: + +- **[Main Library README](../../TriasDev.Templify/README.md)** - Complete API reference with C# examples +- **[Examples.md](../../TriasDev.Templify/Examples.md)** - Extensive code samples +- **[Architecture.md](../../TriasDev.Templify/ARCHITECTURE.md)** - Detailed architecture documentation + +## Quick Installation + +```bash +dotnet add package TriasDev.Templify +``` + +## Basic Usage + +```csharp +using TriasDev.Templify; + +var data = new Dictionary +{ + ["CustomerName"] = "John Doe", + ["OrderDate"] = DateTime.Now.ToString("yyyy-MM-dd") +}; + +var processor = new DocumentTemplateProcessor(); +using var templateStream = File.OpenRead("template.docx"); +using var outputStream = File.Create("output.docx"); + +var result = processor.ProcessTemplate(templateStream, outputStream, data); + +if (result.Success) +{ + Console.WriteLine("Template processed successfully!"); +} +``` + +## Need Help? + +- 🐛 [Report Issues](https://github.com/triasdev/templify/issues) +- 💬 [Discussions](https://github.com/triasdev/templify/discussions) +- 📖 [Current Documentation](../../TriasDev.Templify/README.md) diff --git a/docs/for-template-authors/best-practices.md b/docs/for-template-authors/best-practices.md new file mode 100644 index 0000000..579f809 --- /dev/null +++ b/docs/for-template-authors/best-practices.md @@ -0,0 +1,618 @@ +# Best Practices for Template Authors + +This guide provides practical tips and recommendations for creating maintainable, efficient, and error-free templates. + +## Naming Conventions + +### Use Descriptive Names + +**❌ Bad:** +```json +{ + "n": "Alice", + "d": "2024-01-15", + "amt": 100 +} +``` + +**✅ Good:** +```json +{ + "CustomerName": "Alice", + "InvoiceDate": "2024-01-15", + "TotalAmount": 100 +} +``` + +### Use Consistent Naming Styles + +Pick a style and stick with it throughout your template: + +**PascalCase (recommended):** +```json +{ + "CustomerName": "Alice", + "OrderDate": "2024-01-15", + "ShippingAddress": {...} +} +``` + +**camelCase:** +```json +{ + "customerName": "Alice", + "orderDate": "2024-01-15", + "shippingAddress": {...} +} +``` + +**Avoid:** +- `snake_case` (hard to read in templates) +- `kebab-case` (doesn't work well with dot notation) +- Mixing styles + +### Boolean Names Should Be Questions + +**✅ Good:** +```json +{ + "IsActive": true, + "HasDiscount": true, + "ShouldShowFooter": true, + "CanEdit": true +} +``` + +**❌ Less clear:** +```json +{ + "Active": true, + "Discount": true, + "Footer": true, + "Edit": true +} +``` + +## JSON Structure + +### Group Related Data + +**❌ Flat structure:** +```json +{ + "CustomerName": "Alice", + "CustomerEmail": "alice@example.com", + "CustomerPhone": "+1-555-0123", + "ShippingStreet": "123 Main St", + "ShippingCity": "Springfield", + "ShippingState": "IL", + "BillingStreet": "456 Oak Ave", + "BillingCity": "Chicago", + "BillingState": "IL" +} +``` + +**✅ Nested structure:** +```json +{ + "Customer": { + "Name": "Alice", + "Email": "alice@example.com", + "Phone": "+1-555-0123" + }, + "ShippingAddress": { + "Street": "123 Main St", + "City": "Springfield", + "State": "IL" + }, + "BillingAddress": { + "Street": "456 Oak Ave", + "City": "Chicago", + "State": "IL" + } +} +``` + +### Match JSON to Template Usage + +Structure your data to match how you'll use it in the template. + +**Template needs:** +``` +{{#foreach Departments}} +Department: {{Name}} +Employees: +{{#foreach Employees}} + - {{Name}} +{{/foreach}} +{{/foreach}} +``` + +**JSON structure should match:** +```json +{ + "Departments": [ + { + "Name": "Engineering", + "Employees": [ + { "Name": "Alice" }, + { "Name": "Bob" } + ] + } + ] +} +``` + +## Template Organization + +### Add Comments in Word + +Use Word's comment feature to document your templates: + +1. Select a placeholder or section +2. Right-click → New Comment +3. Explain what data is expected or what the section does + +**Example comments:** +- "This section only shows for VIP customers" +- "Expected format: YYYY-MM-DD" +- "Loop expects array of products with Name, Price, SKU" + +### Break Complex Templates into Sections + +Use clear section headings in your Word document: + +``` +=== CUSTOMER INFORMATION === +{{Customer.Name}} +... + +=== ORDER DETAILS === +{{#foreach Items}} +... +{{/foreach}} + +=== PAYMENT INFORMATION === +... +``` + +### Keep Line Length Reasonable + +**❌ Hard to read:** +``` +{{#if Status = "Active" and HasSubscription and not IsExpired and DaysRemaining > 0 and AccountType = "Premium"}}...{{/if}} +``` + +**✅ Easier to read:** +``` +{{#if Status = "Active" and HasSubscription}} + {{#if not IsExpired and DaysRemaining > 0}} + {{#if AccountType = "Premium"}} + Premium content here + {{/if}} + {{/if}} +{{/if}} +``` + +## Testing + +### Start with Simple Test Data + +Use obvious test values to verify placeholders work: + +**test-data.json:** +```json +{ + "CustomerName": "TEST_NAME", + "Email": "TEST_EMAIL", + "Phone": "TEST_PHONE" +} +``` + +If you see "TEST_NAME" in the output, you know the placeholder works! + +### Test Edge Cases + +Always test with: + +**1. Empty arrays:** +```json +{ + "Items": [] +} +``` + +**2. Single-item arrays:** +```json +{ + "Items": [ + { "Name": "Only Item" } + ] +} +``` + +**3. Many items:** +```json +{ + "Items": [/* 20+ items */] +} +``` + +**4. Minimum/maximum values:** +```json +{ + "Age": 0, + "Score": 100, + "Temperature": -40 +} +``` + +**5. Missing optional fields:** +```json +{ + "Name": "Alice" + // MiddleName intentionally missing +} +``` + +### Validate JSON Before Testing + +Always validate your JSON before using it with Templify: + +1. Go to [jsonlint.com](https://jsonlint.com) +2. Paste your JSON +3. Click "Validate JSON" +4. Fix any errors + +**Common errors:** +- Missing or extra commas +- Missing quotes +- Unclosed brackets/braces + +## Error Prevention + +### Double-Check Placeholder Names + +**Case-sensitive matching:** +- Template: `{{CustomerName}}` +- JSON: `"CustomerName"` ✅ +- JSON: `"customername"` ❌ +- JSON: `"customer_name"` ❌ + +### Verify Closing Tags + +Every opening tag needs a closing tag: + +**❌ Missing closing tag:** +``` +{{#if IsActive}} + Content +← Where's the {{/if}}? +``` + +**✅ Proper closing:** +``` +{{#if IsActive}} + Content +{{/if}} +``` + +### Match Brackets and Braces + +**❌ Wrong:** +``` +{{CustomerName} ← Missing } +{{CustomerName ← Missing }} +{CustomerName}} ← Missing { +``` + +**✅ Correct:** +``` +{{CustomerName}} +``` + +### Avoid Spaces in Placeholders + +**❌ Wrong:** +``` +{{ CustomerName }} ← Spaces inside +{{Customer Name}} ← Space in name +``` + +**✅ Correct:** +``` +{{CustomerName}} +{{Customer.Name}} ← Dot notation is OK +``` + +## Performance Tips + +### Keep Templates Reasonably Sized + +- Templates under 50 pages process quickly +- Templates 50-200 pages may take a few seconds +- Templates over 200 pages may be slow + +Consider splitting very large documents into multiple templates. + +### Limit Loop Nesting + +**✅ Good (2 levels):** +``` +{{#foreach Departments}} + {{#foreach Employees}} + ... + {{/foreach}} +{{/foreach}} +``` + +**⚠️ Avoid (4+ levels):** +``` +{{#foreach A}} + {{#foreach B}} + {{#foreach C}} + {{#foreach D}} + ... (hard to read and maintain) + {{/foreach}} + {{/foreach}} + {{/foreach}} +{{/foreach}} +``` + +### Minimize Conditional Complexity + +**❌ Complex:** +``` +{{#if (Status = "Active" or Status = "Trial") and (Age >= 18 or HasParentConsent) and not (IsBanned or IsExpired) and AccountType = "Premium"}} +``` + +**✅ Simpler (break into steps):** +``` +{{#if Status = "Active" or Status = "Trial"}} + {{#if Age >= 18 or HasParentConsent}} + {{#if not IsBanned and not IsExpired}} + {{#if AccountType = "Premium"}} + ... + {{/if}} + {{/if}} + {{/if}} +{{/if}} +``` + +## Formatting Best Practices + +### Use Format Specifiers + +**❌ Raw values:** +``` +Active: {{IsActive}} → Active: true +Price: {{Price}} → Price: 1234.5 +Date: {{Date}} → Date: 2024-01-15T00:00:00 +``` + +**✅ Formatted:** +``` +Active: {{IsActive:yesno}} → Active: Yes +Price: {{Price:currency}} → Price: $1,234.50 +Date: {{Date:date:MMM d}} → Date: Jan 15 +``` + +### Preserve Template Formatting + +Templify preserves the formatting of your template text: + +- **Bold** text in template stays bold in output +- Font family, size, color are preserved +- Paragraph alignment is maintained +- List formatting (bullets, numbering) is kept + +**Tip:** Format your placeholders in Word to get the formatting you want in the output. + +### Use Markdown for Dynamic Formatting + +When formatting needs to come from data: + +**JSON:** +```json +{ + "Message": "Welcome **Alice**, your account is *active*!" +} +``` + +**Template:** +``` +{{Message}} +``` + +**Output:** +"Welcome **Alice**, your account is *active*!" (with bold and italic applied) + +## Maintenance + +### Version Your Templates + +Keep track of template versions: + +**File naming:** +- `invoice-template-v1.docx` +- `invoice-template-v2.docx` +- `invoice-template-2024-01-15.docx` + +**Or add version in template:** +``` +[Invoice Template v2.3 - Last updated: 2024-01-15] +``` + +### Document Data Requirements + +Create a companion document listing required data: + +**invoice-template-DATA.md:** +```markdown +# Invoice Template Data Requirements + +## Required Fields +- InvoiceNumber (string) +- InvoiceDate (string, format: YYYY-MM-DD) +- Customer.Name (string) +- Customer.Address (string) +- LineItems (array of objects): + - Description (string) + - Quantity (number) + - UnitPrice (number) + - Total (number) + +## Optional Fields +- Customer.TaxID (string) +- DiscountAmount (number) +- Notes (string) +``` + +### Keep Examples Updated + +Maintain a sample JSON file alongside your template: + +**Files:** +- `invoice-template.docx` (the template) +- `invoice-sample-data.json` (sample data) +- `invoice-output-example.docx` (processed example) + +## Common Mistakes to Avoid + +### 1. Forgetting {{/if}} or {{/foreach}} + +**Error:** Template doesn't process correctly + +**Solution:** Count your opening and closing tags: +``` +{{#if A}} ← 1 opening + {{#if B}} ← 2 openings + {{/if}} ← 1 closing +{{/if}} ← 2 closings ✓ +``` + +### 2. Wrong Array Access + +**JSON:** +```json +{ + "Items": [{"Name": "Widget"}] +} +``` + +**❌ Wrong:** +``` +First item: {{Items.Name}} ← Wrong, Items is array not object +``` + +**✅ Correct:** +``` +First item: {{Items[0].Name}} +Or loop: +{{#foreach Items}}{{Name}}{{/foreach}} +``` + +### 3. Comparing Wrong Types + +**JSON:** +```json +{ + "Age": "25" ← String! +} +``` + +**❌ Might not work:** +``` +{{#if Age > 18}} ← Comparing string to number +``` + +**✅ Better:** +```json +{ + "Age": 25 ← Number (no quotes) +} +``` + +### 4. Case Mismatches + +**JSON:** +```json +{ + "customername": "Alice" +} +``` + +**❌ Won't match:** +``` +{{CustomerName}} ← Different case +``` + +**✅ Must match exactly:** +``` +{{customername}} +``` + +### 5. Trailing Commas in JSON + +**❌ Invalid JSON:** +```json +{ + "Name": "Alice", + "Age": 25, ← Extra comma +} +``` + +**✅ Valid JSON:** +```json +{ + "Name": "Alice", + "Age": 25 +} +``` + +## Troubleshooting Checklist + +When things don't work: + +- [ ] Validate JSON at jsonlint.com +- [ ] Check placeholder spelling matches JSON exactly +- [ ] Verify {{...}} has two braces on each side +- [ ] Confirm all {{#if}} have matching {{/if}} +- [ ] Confirm all {{#foreach}} have matching {{/foreach}} +- [ ] Check for spaces inside braces: `{{Name}}` not `{{ Name }}` +- [ ] Verify array access uses [index] not dot notation +- [ ] Test with simple data first +- [ ] Check that JSON types match expectations (numbers not strings) + +## Quick Reference + +### DO: +✅ Use descriptive variable names +✅ Validate JSON before testing +✅ Test with edge cases +✅ Comment complex logic in Word +✅ Group related data in JSON +✅ Use format specifiers +✅ Keep templates organized + +### DON'T: +❌ Use single-letter variable names +❌ Skip JSON validation +❌ Only test happy path +❌ Create overly complex conditionals +❌ Use inconsistent naming styles +❌ Forget closing tags +❌ Mix up case in placeholder names + +## Resources + +- **[Template Syntax Reference](template-syntax.md)** - Complete syntax guide +- **[JSON Basics](json-basics.md)** - Understanding JSON +- **[Examples Gallery](examples-gallery.md)** - Real-world examples +- **[Placeholders Guide](placeholders.md)** - Using placeholders effectively +- **[Conditionals Guide](conditionals.md)** - If/else best practices +- **[Loops Guide](loops.md)** - Working with arrays + +--- + +Remember: Good templates are readable, maintainable, and well-tested. Take time to structure your data properly and document your work – your future self (and others) will thank you! diff --git a/docs/guides/boolean-expressions.md b/docs/for-template-authors/boolean-expressions.md similarity index 100% rename from docs/guides/boolean-expressions.md rename to docs/for-template-authors/boolean-expressions.md diff --git a/docs/for-template-authors/conditionals.md b/docs/for-template-authors/conditionals.md new file mode 100644 index 0000000..9dc2be3 --- /dev/null +++ b/docs/for-template-authors/conditionals.md @@ -0,0 +1,880 @@ +# Conditionals Guide + +Conditionals let you show or hide content in your document based on data values. They're perfect for creating flexible templates that adapt to different scenarios. + +## Basic Conditional Syntax + +### Simple If Statement + +``` +{{#if VariableName}} + Content to show if true +{{/if}} +``` + +**When content is shown:** +- Variable exists and is `true` +- Variable exists and is not empty/zero/false + +**JSON:** +```json +{ + "IsVIP": true +} +``` + +**Template:** +``` +Dear Customer, + +{{#if IsVIP}} +Thank you for being a VIP member! You get 20% off today. +{{/if}} + +Best regards +``` + +**Output (when IsVIP is true):** +``` +Dear Customer, + +Thank you for being a VIP member! You get 20% off today. + +Best regards +``` + +### If-Else Statement + +``` +{{#if Condition}} + Content when true +{{else}} + Content when false +{{/if}} +``` + +**JSON:** +```json +{ + "IsPremium": false +} +``` + +**Template:** +``` +{{#if IsPremium}} +Welcome, Premium Member! Enjoy unlimited access. +{{else}} +Upgrade to Premium to unlock all features. +{{/if}} +``` + +**Output:** +``` +Upgrade to Premium to unlock all features. +``` + +## Comparison Operators + +### Equality (`=`) + +Check if two values are equal: + +**JSON:** +```json +{ + "Status": "Active" +} +``` + +**Template:** +``` +{{#if Status = "Active"}} +Your account is active and ready to use. +{{/if}} + +{{#if Status = "Pending"}} +Your account is pending approval. +{{/if}} +``` + +**Tips:** +- Use quotes around text values: `Status = "Active"` +- Numbers don't need quotes: `Age = 18` +- Comparison is case-sensitive: `"active"` ≠ `"Active"` + +### Inequality (`!=`) + +Check if two values are NOT equal: + +**JSON:** +```json +{ + "PaymentStatus": "Paid" +} +``` + +**Template:** +``` +{{#if PaymentStatus != "Paid"}} +⚠️ Payment required - please submit payment to proceed. +{{/if}} +``` + +This message only shows when payment is NOT paid. + +### Greater Than (`>`) + +**JSON:** +```json +{ + "Age": 25, + "Score": 95 +} +``` + +**Template:** +``` +{{#if Age > 18}} +You are eligible to vote. +{{/if}} + +{{#if Score > 90}} +Excellent work! You earned an A grade. +{{/if}} +``` + +### Less Than (`<`) + +**JSON:** +```json +{ + "Temperature": -5, + "Stock": 3 +} +``` + +**Template:** +``` +{{#if Temperature < 0}} +⚠️ Freezing conditions - take precautions. +{{/if}} + +{{#if Stock < 5}} +⚠️ Low stock alert - only {{Stock}} items remaining. +{{/if}} +``` + +### Greater Than or Equal (`>=`) + +**JSON:** +```json +{ + "Years + +Experience": 5, + "MinimumOrder": 100, + "OrderAmount": 100 +} +``` + +**Template:** +``` +{{#if YearsExperience >= 5}} +You qualify for the Senior Developer position. +{{/if}} + +{{#if OrderAmount >= MinimumOrder}} +✓ Order qualifies for free shipping! +{{/if}} +``` + +### Less Than or Equal (`<=`) + +**JSON:** +```json +{ + "ItemsInCart": 3, + "DaysUntilExpiry": 7 +} +``` + +**Template:** +``` +{{#if ItemsInCart <= 5}} +Add more items to qualify for bulk discount! +{{/if}} + +{{#if DaysUntilExpiry <= 7}} +⚠️ Your subscription expires soon - renew now! +{{/if}} +``` + +## Logical Operators + +### AND Operator + +Both conditions must be true: + +``` +{{#if Condition1 and Condition2}} + Content +{{/if}} +``` + +**JSON:** +```json +{ + "Age": 25, + "HasLicense": true, + "HasInsurance": true +} +``` + +**Template:** +``` +{{#if Age >= 18 and HasLicense}} +You can rent a car. +{{/if}} + +{{#if HasLicense and HasInsurance}} +You're approved for vehicle rental. +{{/if}} +``` + +### OR Operator + +At least one condition must be true: + +``` +{{#if Condition1 or Condition2}} + Content +{{/if}} +``` + +**JSON:** +```json +{ + "IsVIP": false, + "IsPremium": true, + "Role": "Admin" +} +``` + +**Template:** +``` +{{#if IsVIP or IsPremium}} +You have access to exclusive features. +{{/if}} + +{{#if Role = "Admin" or Role = "Moderator"}} +You have moderation permissions. +{{/if}} +``` + +### NOT Operator + +Negates a condition: + +``` +{{#if not Condition}} + Content +{{/if}} +``` + +**JSON:** +```json +{ + "IsExpired": false, + "IsBanned": false +} +``` + +**Template:** +``` +{{#if not IsExpired}} +Your subscription is active. +{{/if}} + +{{#if not IsBanned}} +Welcome back! Your account is in good standing. +{{/if}} +``` + +### Combining Multiple Operators + +**JSON:** +```json +{ + "Age": 25, + "Country": "USA", + "HasPassport": true, + "IsBanned": false +} +``` + +**Template:** +``` +{{#if Age >= 18 and (Country = "USA" or HasPassport) and not IsBanned}} +You are eligible to travel internationally. +{{/if}} +``` + +**Operator precedence:** +1. Parentheses `()` +2. `not` +3. Comparison operators (`=`, `!=`, `>`, `<`, `>=`, `<=`) +4. `and` +5. `or` + +## Common Patterns + +### Boolean Flags + +**JSON:** +```json +{ + "ShowHeader": true, + "ShowFooter": false, + "EnableTracking": true +} +``` + +**Template:** +``` +{{#if ShowHeader}} +=== HEADER SECTION === +Company Name | Contact Info +{{/if}} + +[Main content here] + +{{#if ShowFooter}} +=== FOOTER SECTION === +© 2024 Company Name +{{/if}} +``` + +### Status Checks + +**JSON:** +```json +{ + "OrderStatus": "Shipped" +} +``` + +**Template:** +``` +Order Status: + +{{#if OrderStatus = "Pending"}} +⏳ Your order is being processed. +{{/if}} + +{{#if OrderStatus = "Shipped"}} +📦 Your order has been shipped! +{{/if}} + +{{#if OrderStatus = "Delivered"}} +✓ Your order was delivered. +{{/if}} + +{{#if OrderStatus = "Cancelled"}} +❌ This order was cancelled. +{{/if}} +``` + +### Tiered Messaging + +**JSON:** +```json +{ + "Score": 85 +} +``` + +**Template:** +``` +Your Score: {{Score}} + +{{#if Score >= 90}} +🏆 Outstanding! You achieved an A grade. +{{else}} + {{#if Score >= 80}} + 👍 Great job! You achieved a B grade. + {{else}} + {{#if Score >= 70}} + ✓ Good work! You achieved a C grade. + {{else}} + 📚 Keep studying! You can improve. + {{/if}} + {{/if}} +{{/if}} +``` + +**Note:** Nested conditionals work, but try to keep them simple for readability. + +### Access Control + +**JSON:** +```json +{ + "UserRole": "Admin", + "IsAuthenticated": true +} +``` + +**Template:** +``` +{{#if IsAuthenticated}} +Welcome to the dashboard! + +{{#if UserRole = "Admin"}} +[Admin Panel] +- User Management +- System Settings +- Reports +{{/if}} + +{{#if UserRole = "Editor"}} +[Editor Panel] +- Edit Content +- Publish Articles +{{/if}} + +{{#if UserRole = "Viewer"}} +[Viewer Panel] +- View Content Only +{{/if}} + +{{else}} +Please log in to access this page. +{{/if}} +``` + +## Working with Numbers + +### Range Checks + +**JSON:** +```json +{ + "Age": 35, + "Temperature": 72, + "Price": 150 +} +``` + +**Template:** +``` +{{#if Age >= 18 and Age < 65}} +Adult pricing applies. +{{/if}} + +{{#if Temperature >= 60 and Temperature <= 80}} +Perfect weather today! +{{/if}} + +{{#if Price > 100 and Price <= 200}} +Mid-range product pricing. +{{/if}} +``` + +### Inventory Checks + +**JSON:** +```json +{ + "StockLevel": 5, + "ReorderPoint": 10 +} +``` + +**Template:** +``` +{{#if StockLevel = 0}} +❌ OUT OF STOCK +{{else}} + {{#if StockLevel < ReorderPoint}} + ⚠️ LOW STOCK: {{StockLevel}} remaining + {{else}} + ✓ In Stock: {{StockLevel}} available + {{/if}} +{{/if}} +``` + +### Discount Qualification + +**JSON:** +```json +{ + "OrderTotal": 150, + "IsFirstOrder": false, + "LoyaltyPoints": 500 +} +``` + +**Template:** +``` +{{#if OrderTotal >= 100 or IsFirstOrder or LoyaltyPoints >= 1000}} +🎉 You qualify for a discount! + +{{#if OrderTotal >= 100}} + - Free shipping on orders over $100 +{{/if}} + +{{#if IsFirstOrder}} + - 15% off first order discount +{{/if}} + +{{#if LoyaltyPoints >= 1000}} + - Loyalty member discount available +{{/if}} +{{/if}} +``` + +## Working with Text + +### Text Comparison + +Remember: text comparisons are **case-sensitive**! + +**JSON:** +```json +{ + "Category": "Electronics", + "Priority": "high" +} +``` + +**Template:** +``` +{{#if Category = "Electronics"}} +Shipping: 2-3 business days +{{/if}} + +{{#if Category = "electronics"}} +This won't match - wrong case! +{{/if}} + +{{#if Priority = "high"}} +⚠️ HIGH PRIORITY ORDER +{{/if}} +``` + +### Multiple Text Options + +**JSON:** +```json +{ + "PaymentMethod": "Credit Card" +} +``` + +**Template:** +``` +{{#if PaymentMethod = "Credit Card" or PaymentMethod = "Debit Card"}} +Card payment processing fee: $2.50 +{{/if}} + +{{#if PaymentMethod = "PayPal" or PaymentMethod = "Venmo"}} +Online payment processing fee: 3% +{{/if}} + +{{#if PaymentMethod = "Cash" or PaymentMethod = "Check"}} +No processing fees! +{{/if}} +``` + +## Nested Conditionals + +You can nest conditionals inside each other: + +**JSON:** +```json +{ + "IsLoggedIn": true, + "UserType": "Premium", + "HasActiveSubscription": true +} +``` + +**Template:** +``` +{{#if IsLoggedIn}} + Welcome! + + {{#if UserType = "Premium"}} + {{#if HasActiveSubscription}} + [Premium Content Unlocked] + Access to all features! + {{else}} + [Subscription Expired] + Please renew your subscription. + {{/if}} + {{else}} + [Free Account] + Upgrade to Premium for more features. + {{/if}} +{{else}} + Please log in. +{{/if}} +``` + +**Best Practice:** Limit nesting to 2-3 levels deep to keep templates readable. + +## Conditionals with Loops + +You can use conditionals inside loops: + +**JSON:** +```json +{ + "Products": [ + { "Name": "Widget", "Price": 10, "InStock": true }, + { "Name": "Gadget", "Price": 25, "InStock": false }, + { "Name": "Doohickey", "Price": 15, "InStock": true } + ] +} +``` + +**Template:** +``` +Product List: + +{{#foreach Products}} +- {{Name}}: ${{Price}} + {{#if InStock}} + ✓ Available + {{else}} + ❌ Out of Stock + {{/if}} +{{/foreach}} +``` + +**Output:** +``` +Product List: + +- Widget: $10 + ✓ Available +- Gadget: $25 + ❌ Out of Stock +- Doohickey: $15 + ✓ Available +``` + +## Loop Variables in Conditionals + +Use loop-specific variables in conditionals: + +**JSON:** +```json +{ + "Items": ["Apple", "Banana", "Cherry", "Date"] +} +``` + +**Template:** +``` +{{#foreach Items}} +{{#if @first}}*** First item: {{.}} ***{{/if}} +{{#if not @first and not @last}}- {{.}}{{/if}} +{{#if @last}}*** Last item: {{.}} ***{{/if}} +{{/foreach}} +``` + +**Output:** +``` +*** First item: Apple *** +- Banana +- Cherry +*** Last item: Date *** +``` + +## Common Use Cases + +### Personalized Greetings + +**JSON:** +```json +{ + "CustomerName": "Alice", + "LastPurchaseDate": "2024-01-10", + "DaysSinceLastPurchase": 45 +} +``` + +**Template:** +``` +Dear {{CustomerName}}, + +{{#if DaysSinceLastPurchase < 30}} +Great to see you again so soon! +{{else}} +We've missed you! It's been a while since your last visit. +{{/if}} + +{{#if DaysSinceLastPurchase > 60}} +Here's a 15% discount to welcome you back! +{{/if}} +``` + +### Terms and Conditions + +**JSON:** +```json +{ + "IncludeWarranty": true, + "IncludeInsurance": false, + "IncludeExtendedSupport": true +} +``` + +**Template:** +``` +TERMS AND CONDITIONS + +{{#if IncludeWarranty}} +1. Warranty Coverage + This product includes a 2-year manufacturer warranty... +{{/if}} + +{{#if IncludeInsurance}} +2. Insurance Policy + Additional insurance coverage provides... +{{/if}} + +{{#if IncludeExtendedSupport}} +3. Extended Support + 24/7 customer support is included for... +{{/if}} +``` + +### Regional Content + +**JSON:** +```json +{ + "Country": "USA", + "Language": "English" +} +``` + +**Template:** +``` +{{#if Country = "USA"}} +Customer Service: 1-800-555-0123 +Business Hours: 9 AM - 5 PM EST +{{/if}} + +{{#if Country = "UK"}} +Customer Service: 0800 123 4567 +Business Hours: 9 AM - 5 PM GMT +{{/if}} + +{{#if Country = "Germany"}} +Kundenservice: 0800 123 4567 +Geschäftszeiten: 9:00 - 17:00 Uhr MEZ +{{/if}} +``` + +## Troubleshooting + +### Conditional Not Working + +**Check these common issues:** + +1. **Syntax errors:** + - ✅ `{{#if Status = "Active"}}` + - ❌ `{{if Status = "Active"}}` (missing `#`) + - ❌ `{{#if Status = "Active"` (missing closing `}}`) + +2. **Missing closing tag:** + - ✅ `{{#if ...}}...{{/if}}` + - ❌ `{{#if ...}}...{{#endif}}` (wrong closing tag) + +3. **Case sensitivity:** + - ✅ `{{#if Status = "Active"}}` with JSON: `"Status": "Active"` + - ❌ `{{#if Status = "active"}}` with JSON: `"Status": "Active"}` + +4. **Wrong operator:** + - ✅ `{{#if Age = 18}}` (checking equality) + - ❌ `{{#if Age == 18}}` (wrong operator, use single `=`) + +5. **Quotes around text:** + - ✅ `{{#if Name = "Alice"}}` + - ❌ `{{#if Name = Alice}}` (missing quotes) + +6. **Comparing wrong types:** + - ✅ `{{#if Age > 18}}` with JSON: `"Age": 25` (number) + - ⚠️ `{{#if Age > 18}}` with JSON: `"Age": "25"` (string - may not work as expected) + +### Content Always Shows/Never Shows + +**Debug steps:** + +1. **Print the variable value** to see what you're working with: + ``` + Status value: {{Status}} + {{#if Status = "Active"}}Content{{/if}} + ``` + +2. **Check JSON structure:** + ```json + { + "Status": "Active" ← Should match exactly + } + ``` + +3. **Simplify the condition:** + Start with a simple boolean: + ``` + {{#if IsActive}}Content{{/if}} + ``` + +### Nested Conditionals Not Working + +Make sure each `{{#if}}` has a matching `{{/if}}`: + +**❌ Wrong:** +``` +{{#if A}} + {{#if B}} + Content + {{/if}} + ← Missing {{/if}} for A! +``` + +**✅ Correct:** +``` +{{#if A}} + {{#if B}} + Content + {{/if}} +{{/if}} +``` + +## Best Practices + +1. **Keep conditions simple** - Break complex logic into multiple simpler conditions +2. **Use meaningful variable names** - `IsEligibleForDiscount` is better than `Flag1` +3. **Test edge cases** - What happens when values are null, zero, empty, etc.? +4. **Add comments in Word** - Use Word comments to document complex conditional logic +5. **Use else clauses** - Provide feedback for both true and false cases when appropriate +6. **Limit nesting** - Deep nesting is hard to read; try to keep it to 2-3 levels maximum + +## Next Steps + +- **[Loops Guide](loops.md)** - Repeat content for arrays and lists +- **[Boolean Expressions](boolean-expressions.md)** - Advanced boolean expression techniques +- **[Placeholders Guide](placeholders.md)** - Using variables in your templates +- **[Template Syntax Reference](template-syntax.md)** - Complete syntax guide +- **[Examples Gallery](examples-gallery.md)** - Real-world examples + +## Related Topics + +- [Format Specifiers](format-specifiers.md) - Display boolean values as Yes/No, checkboxes, etc. +- [Best Practices](best-practices.md) - Tips for maintainable templates +- [JSON Basics](json-basics.md) - Understanding your data structure diff --git a/docs/for-template-authors/examples-gallery.md b/docs/for-template-authors/examples-gallery.md new file mode 100644 index 0000000..0d8c175 --- /dev/null +++ b/docs/for-template-authors/examples-gallery.md @@ -0,0 +1,146 @@ +# Examples Gallery + +Explore real-world Templify templates with visual examples. Each example includes the template image, sample JSON data, and the resulting output. + +## Available Examples + +### 1. Hello World + +**Description:** A simple introduction template demonstrating basic placeholder replacement. + +**Features:** +- Simple placeholders +- Text replacement + +**Template:** + +![Hello World Template](../images/examples/templates/hello-world-template.png) + +**Output:** + +![Hello World Output](../images/examples/outputs/hello-world-output.png) + +**Download:** *(Coming soon)* +- [template.docx](#) - The Word template file +- [data.json](#) - Sample JSON data + +--- + +### 2. Invoice Generator + +**Description:** A professional invoice template with line items, calculations, and customer information. + +**Features:** +- Nested properties (Customer.Name, Customer.Address) +- Table row loops for line items +- Number formatting +- Conditional sections + +**Template:** + +![Invoice Template](../images/examples/templates/invoice-template.png) + +**Output:** + +![Invoice Output](../images/examples/outputs/invoice-output.png) + +**Download:** *(Coming soon)* +- [template.docx](#) - The Word template file +- [data.json](#) - Sample JSON data with multiple line items + +--- + +### 3. Conditional Content + +**Description:** Demonstrates conditional sections that show/hide based on data values. + +**Features:** +- If/else conditionals +- Boolean flags +- Status-based messaging +- Multiple conditional sections + +**Template:** + +![Conditionals Template](../images/examples/templates/conditionals-template.png) + +**Output:** + +![Conditionals Output](../images/examples/outputs/conditionals-output.png) + +**Download:** *(Coming soon)* +- [template.docx](#) - The Word template file +- [data.json](#) - Sample JSON data with various status values + +--- + +## More Examples Coming Soon! + +We're working on adding more examples covering: + +- **Nested Loops** - Departments with employees +- **Report Card** - Student grades with loops and conditionals +- **Certificate** - Formal document with formatting +- **Meeting Notes** - Attendees list with roles +- **Product Catalog** - Categories with product listings +- **Contract Template** - Terms and conditions with optional clauses + +## Using These Examples + +### Try Them Yourself + +1. Download the template file (.docx) +2. Download the sample JSON data (.json) +3. Process the template using: + - **GUI Application:** Open Templify GUI, select the template and JSON file + - **CLI Tool:** Run `templify process template.docx --data data.json --output output.docx` + +### Modify for Your Needs + +Each example template can be customized: + +1. Open the template in Microsoft Word +2. Modify the placeholders, add new sections, change formatting +3. Update the JSON data to match your changes +4. Process and see your customized output! + +## Tips for Learning + +### Start Simple + +Begin with the Hello World example to understand basic placeholders, then move to more complex examples. + +### Study the JSON Structure + +Look at how the JSON data is structured and how it maps to the template placeholders. This will help you understand the dot notation (`Customer.Name`) and arrays. + +### Experiment + +- Change values in the JSON to see how the output changes +- Add new placeholders to the template +- Try removing conditional sections +- Add more items to loops + +### Check the Output + +Always compare the template to the output to see exactly what Templify does. This helps you understand: +- Where data gets inserted +- How loops repeat content +- When conditionals show/hide sections + +## Need Help? + +If you have questions about these examples: + +- **[Getting Started Guide](getting-started.md)** - Basic template creation tutorial +- **[Template Syntax Reference](template-syntax.md)** - Complete syntax guide +- **[Best Practices](best-practices.md)** - Tips for creating good templates +- **[FAQ](../FAQ.md)** - Common questions and answers + +## Contribute Your Examples + +Do you have a great template example? We'd love to include it! Check out our [Contributing Guide](../../CONTRIBUTING.md) to learn how to submit your examples. + +--- + +*Examples are automatically generated using the Templify DocumentGenerator tool to ensure they stay up-to-date with the latest features.* diff --git a/docs/guides/format-specifiers.md b/docs/for-template-authors/format-specifiers.md similarity index 100% rename from docs/guides/format-specifiers.md rename to docs/for-template-authors/format-specifiers.md diff --git a/docs/for-template-authors/getting-started.md b/docs/for-template-authors/getting-started.md new file mode 100644 index 0000000..7b21c8d --- /dev/null +++ b/docs/for-template-authors/getting-started.md @@ -0,0 +1,248 @@ +# Getting Started with Templify + +Welcome! This guide will help you create your first Word document template with Templify. **No programming experience required** - if you can use Microsoft Word and edit a simple text file, you can create templates. + +## What is Templify? + +Templify is a tool that takes a Word document template with placeholders (like `{{CompanyName}}`) and fills them in with your data to create personalized documents. Think of it like mail merge, but more powerful. + +## What You'll Need + +1. **Microsoft Word** (or any app that can edit .docx files) +2. **A text editor** (Notepad, TextEdit, VS Code, or any editor for JSON files) +3. **Templify** (either the GUI app or CLI tool) + +## Your First Template in 5 Minutes + +### Step 1: Create a Word Template + +1. Open Microsoft Word +2. Create a new document +3. Type some text with placeholders in double curly braces: + +``` +Hello {{Name}}! + +Welcome to {{CompanyName}}. We're excited to have you on board. + +Your account has been created with the email: {{Email}} +``` + +4. Save the document as `welcome-letter.docx` + +**That's it!** You've created your first template. The text inside `{{...}}` are placeholders that will be replaced with actual data. + +### Step 2: Prepare Your Data (JSON) + +Now you need to provide the data to fill in those placeholders. We use a format called JSON (don't worry, it's simple!). + +Create a file called `data.json` with this content: + +```json +{ + "Name": "Alice Johnson", + "CompanyName": "Acme Corporation", + "Email": "alice.johnson@acme.com" +} +``` + +**Understanding the structure:** +- The whole thing is wrapped in `{ }` (curly braces) +- Each piece of data is written as `"PlaceholderName": "Value"` +- Separate each piece with a comma +- Text values need double quotes around them + +### Step 3: Process Your Template + +Now you'll combine the template with the data to create the final document. + +#### Option A: Using the GUI Application + +1. Open the Templify GUI application +2. Click "Select Template" and choose `welcome-letter.docx` +3. Click "Select Data" and choose `data.json` +4. Click "Process Template" +5. Save the output as `welcome-letter-final.docx` + +#### Option B: Using the Command Line + +If you have the CLI tool installed, run: + +```bash +templify process welcome-letter.docx --data data.json --output welcome-letter-final.docx +``` + +### Step 4: View the Result + +Open `welcome-letter-final.docx` in Word. You should see: + +``` +Hello Alice Johnson! + +Welcome to Acme Corporation. We're excited to have you on board. + +Your account has been created with the email: alice.johnson@acme.com +``` + +**Congratulations!** You've created and processed your first template! 🎉 + +## What You Can Do With Templates + +### Simple Placeholders + +Replace any text with data from your JSON file: + +**Template:** +``` +Customer: {{CustomerName}} +Order Number: {{OrderNumber}} +Date: {{OrderDate}} +``` + +**Data (data.json):** +```json +{ + "CustomerName": "Bob Smith", + "OrderNumber": "ORD-12345", + "OrderDate": "2024-01-15" +} +``` + +### Nested Data + +Access data within data using dots (`.`): + +**Template:** +``` +Name: {{Customer.Name}} +City: {{Customer.Address.City}} +Country: {{Customer.Address.Country}} +``` + +**Data (data.json):** +```json +{ + "Customer": { + "Name": "Sarah Connor", + "Address": { + "City": "Los Angeles", + "Country": "USA" + } + } +} +``` + +### Conditional Content + +Show or hide content based on conditions: + +**Template:** +``` +{{#if IsPremium}} +Thank you for being a Premium member! +{{/if}} + +{{#if Status = "Active"}} +Your account is active. +{{else}} +Your account needs activation. +{{/if}} +``` + +**Data (data.json):** +```json +{ + "IsPremium": true, + "Status": "Active" +} +``` + +### Repeating Content (Loops) + +Repeat content for each item in a list: + +**Template:** +``` +Your order contains: + +{{#foreach Items}} +- {{Name}}: ${{Price}} +{{/foreach}} +``` + +**Data (data.json):** +```json +{ + "Items": [ + { "Name": "Widget", "Price": "10.00" }, + { "Name": "Gadget", "Price": "25.00" }, + { "Name": "Doohickey", "Price": "15.00" } + ] +} +``` + +**Result:** +``` +Your order contains: + +- Widget: $10.00 +- Gadget: $25.00 +- Doohickey: $15.00 +``` + +## Common Mistakes to Avoid + +### ❌ Wrong Placeholder Syntax + +``` +{Name} ← Only one curly brace (needs two) +{{Name} ← Missing closing braces +{{ Name }} ← Spaces inside (remove them) +``` + +### ✅ Correct Placeholder Syntax + +``` +{{Name}} ← Perfect! +``` + +### ❌ Invalid JSON + +```json +{ + Name: "Alice" ← Missing quotes around the key + "Email": alice@... ← Missing quotes around the value + "Age": 25, ← Extra comma at the end +} +``` + +### ✅ Valid JSON + +```json +{ + "Name": "Alice", + "Email": "alice@email.com", + "Age": 25 +} +``` + +**Tip:** Use a JSON validator website (like jsonlint.com) to check your JSON files if you get errors. + +## Next Steps + +Now that you've created your first template, explore more advanced features: + +- **[JSON Basics](json-basics.md)** - Learn more about JSON data format +- **[Placeholders](placeholders.md)** - All the ways to use placeholders +- **[Conditionals](conditionals.md)** - Show/hide content with if/else +- **[Loops](loops.md)** - Repeat content for lists and tables +- **[Format Specifiers](format-specifiers.md)** - Format numbers, dates, and more +- **[Best Practices](best-practices.md)** - Tips for creating maintainable templates + +## Need Help? + +- **Can't find your placeholder?** Make sure the name in `{{...}}` exactly matches the name in your JSON (including uppercase/lowercase) +- **Getting an error?** Check that your JSON is valid using a JSON validator +- **Template not changing?** Make sure you're opening the **output** file, not the original template + +For more help, check out our [Examples Gallery](examples-gallery.md) with downloadable templates you can study and modify. diff --git a/docs/for-template-authors/json-basics.md b/docs/for-template-authors/json-basics.md new file mode 100644 index 0000000..5fa2f03 --- /dev/null +++ b/docs/for-template-authors/json-basics.md @@ -0,0 +1,485 @@ +# JSON Basics for Template Authors + +JSON (JavaScript Object Notation) is a simple way to store and organize data in a text file. Don't let the technical-sounding name intimidate you - it's actually quite straightforward once you understand the basics! + +## Why JSON? + +Templify uses JSON to provide the data that fills in your template placeholders. Think of JSON as a way to write down information in a structured format that computers can easily read. + +## The Five Types of Data in JSON + +### 1. Text (Strings) + +Text values must be wrapped in double quotes: + +```json +{ + "Name": "Alice Johnson", + "Email": "alice@example.com", + "Message": "Hello, world!" +} +``` + +**Rules for text:** +- Always use **double quotes** (`"`) not single quotes (`'`) +- To include a quote inside text, use `\"`: `"She said \"Hello\""` +- To include a backslash, use `\\`: `"C:\\Users\\Documents"` + +### 2. Numbers + +Numbers don't need quotes: + +```json +{ + "Age": 25, + "Price": 19.99, + "Quantity": 100, + "Temperature": -5.5 +} +``` + +**Rules for numbers:** +- No quotes around numbers +- Use a dot (`.`) for decimals, not a comma +- Negative numbers start with `-` + +### 3. True/False (Booleans) + +For yes/no values, use `true` or `false` (no quotes, all lowercase): + +```json +{ + "IsActive": true, + "IsPremium": false, + "HasDiscount": true +} +``` + +**Rules for true/false:** +- Must be lowercase: `true` or `false` +- No quotes around them +- These are perfect for conditionals in your templates + +### 4. Lists (Arrays) + +Lists let you have multiple values. They're wrapped in square brackets `[ ]`: + +```json +{ + "Colors": ["Red", "Green", "Blue"], + "Prices": [10.99, 25.00, 15.50], + "Tags": ["new", "featured", "sale"] +} +``` + +**Rules for lists:** +- Wrap the list in square brackets: `[ ]` +- Separate items with commas +- All items should be the same type (all text, all numbers, etc.) +- Can be empty: `[]` + +### 5. Objects (Nested Data) + +Objects let you group related data together. They're wrapped in curly braces `{ }`: + +```json +{ + "Customer": { + "Name": "Bob Smith", + "Age": 30, + "Email": "bob@example.com" + } +} +``` + +**Rules for objects:** +- Wrap in curly braces: `{ }` +- Each piece of data is `"key": value` +- Separate pieces with commas +- Can contain any type of data, including other objects and lists + +## Complete JSON File Structure + +Every JSON file for Templify follows this pattern: + +```json +{ + "Field1": "value", + "Field2": "value", + "Field3": "value" +} +``` + +**Key rules:** +1. **Start with `{` and end with `}`** - These wrap everything +2. **Each line has `"Name": value`** - The name (key) and its value +3. **Separate lines with commas** - But NOT after the last line +4. **Names (keys) must have double quotes** - Values depend on the type + +## Common Examples + +### Simple Contact Information + +```json +{ + "FirstName": "Sarah", + "LastName": "Connor", + "Phone": "+1-555-0123", + "Age": 28, + "IsSubscribed": true +} +``` + +### Nested Information (Objects Within Objects) + +```json +{ + "Company": { + "Name": "Acme Corp", + "Founded": 1995, + "Address": { + "Street": "123 Main St", + "City": "Springfield", + "Country": "USA" + } + } +} +``` + +**In your template, access nested data with dots:** +- `{{Company.Name}}` → "Acme Corp" +- `{{Company.Address.City}}` → "Springfield" + +### Lists of Items + +```json +{ + "CustomerName": "John Doe", + "OrderItems": [ + { + "ProductName": "Widget", + "Quantity": 2, + "Price": 10.00 + }, + { + "ProductName": "Gadget", + "Quantity": 1, + "Price": 25.00 + } + ] +} +``` + +**In your template, use loops:** + +``` +{{#foreach OrderItems}} +- {{ProductName}}: ${{Price}} (Qty: {{Quantity}}) +{{/foreach}} +``` + +### Combining Everything + +```json +{ + "CustomerName": "Alice Johnson", + "IsVIP": true, + "TotalSpent": 1250.50, + "RecentOrders": [ + { + "OrderDate": "2024-01-15", + "Amount": 50.00, + "Status": "Delivered" + }, + { + "OrderDate": "2024-02-20", + "Amount": 75.00, + "Status": "Shipped" + } + ], + "PreferredContact": { + "Method": "Email", + "Value": "alice@example.com", + "SendPromotions": true + } +} +``` + +## Common Mistakes and How to Fix Them + +### ❌ Missing Comma + +**Wrong:** +```json +{ + "Name": "Alice" + "Age": 25 +} +``` + +**Right:** +```json +{ + "Name": "Alice", + "Age": 25 +} +``` + +### ❌ Extra Comma at the End + +**Wrong:** +```json +{ + "Name": "Alice", + "Age": 25, +} +``` + +**Right:** +```json +{ + "Name": "Alice", + "Age": 25 +} +``` + +### ❌ Missing Quotes Around Keys + +**Wrong:** +```json +{ + Name: "Alice", + Age: 25 +} +``` + +**Right:** +```json +{ + "Name": "Alice", + "Age": 25 +} +``` + +### ❌ Single Quotes Instead of Double Quotes + +**Wrong:** +```json +{ + 'Name': 'Alice', + 'Age': 25 +} +``` + +**Right:** +```json +{ + "Name": "Alice", + "Age": 25 +} +``` + +### ❌ Numbers in Quotes + +If you put numbers in quotes, they become text (usually fine, but can cause issues with comparisons): + +**Less than ideal:** +```json +{ + "Age": "25", + "Price": "19.99" +} +``` + +**Better:** +```json +{ + "Age": 25, + "Price": 19.99 +} +``` + +### ❌ Missing Closing Bracket or Brace + +**Wrong:** +```json +{ + "Items": ["Apple", "Banana", "Cherry" +} +``` + +**Right:** +```json +{ + "Items": ["Apple", "Banana", "Cherry"] +} +``` + +## Practical Tips + +### Start Small + +Begin with simple data and gradually add complexity: + +**Step 1: Simple** +```json +{ + "Name": "Test" +} +``` + +**Step 2: Add More** +```json +{ + "Name": "Test", + "Email": "test@example.com" +} +``` + +**Step 3: Add Nesting** +```json +{ + "Name": "Test", + "Email": "test@example.com", + "Address": { + "City": "Springfield" + } +} +``` + +### Use a JSON Validator + +Before using your JSON file with Templify, validate it: + +1. Go to [jsonlint.com](https://jsonlint.com) +2. Paste your JSON +3. Click "Validate JSON" +4. Fix any errors it reports + +### Use a Good Text Editor + +Some text editors help you write JSON: + +- **VS Code** - Free, shows errors as you type, auto-indents +- **Notepad++** - Free, syntax highlighting +- **Sublime Text** - Free trial, clean interface + +Avoid Microsoft Word or rich-text editors - they add invisible formatting that breaks JSON! + +### Format for Readability + +**Hard to read:** +```json +{"Name":"Alice","Age":25,"City":"NYC"} +``` + +**Easy to read:** +```json +{ + "Name": "Alice", + "Age": 25, + "City": "NYC" +} +``` + +Most text editors can auto-format JSON for you (often with `Shift+Alt+F` or a "Format Document" command). + +## How JSON Maps to Template Placeholders + +The structure of your JSON determines how you write placeholders: + +### Simple Fields + +**JSON:** +```json +{ + "CustomerName": "Alice" +} +``` + +**Template:** +``` +Customer: {{CustomerName}} +``` + +### Nested Fields + +**JSON:** +```json +{ + "Customer": { + "Name": "Alice", + "Email": "alice@example.com" + } +} +``` + +**Template:** +``` +Name: {{Customer.Name}} +Email: {{Customer.Email}} +``` + +### Lists/Arrays + +**JSON:** +```json +{ + "Items": [ + { "Name": "Widget", "Price": 10 }, + { "Name": "Gadget", "Price": 20 } + ] +} +``` + +**Template:** +``` +{{#foreach Items}} +- {{Name}}: ${{Price}} +{{/foreach}} +``` + +### Array Item by Index + +**JSON:** +```json +{ + "Colors": ["Red", "Green", "Blue"] +} +``` + +**Template:** +``` +First color: {{Colors[0]}} +Second color: {{Colors[1]}} +``` + +## Quick Reference + +| Data Type | JSON Example | Template Example | +|-----------|-------------|------------------| +| Text | `"Name": "Alice"` | `{{Name}}` | +| Number | `"Age": 25` | `{{Age}}` | +| True/False | `"IsActive": true` | `{{#if IsActive}}...{{/if}}` | +| Nested Object | `"Customer": { "Name": "Alice" }` | `{{Customer.Name}}` | +| List | `"Items": ["A", "B"]` | `{{#foreach Items}}{{.}}{{/foreach}}` | + +## Next Steps + +Now that you understand JSON basics, learn how to use it in your templates: + +- **[Placeholders](placeholders.md)** - Using data in your templates +- **[Conditionals](conditionals.md)** - Showing content based on data values +- **[Loops](loops.md)** - Repeating content for lists +- **[Getting Started](getting-started.md)** - Complete beginner tutorial + +## Need Help? + +If you're stuck: + +1. **Validate your JSON** at jsonlint.com +2. **Check for common mistakes** (missing commas, quotes, brackets) +3. **Start simple** and add complexity gradually +4. **Look at examples** in our [Examples Gallery](examples-gallery.md) + +Remember: Everyone makes JSON mistakes at first. Use a validator, and you'll get the hang of it quickly! diff --git a/docs/for-template-authors/loops.md b/docs/for-template-authors/loops.md new file mode 100644 index 0000000..07d0769 --- /dev/null +++ b/docs/for-template-authors/loops.md @@ -0,0 +1,898 @@ +# Loops Guide + +Loops let you repeat content for each item in a list. They're essential for creating dynamic documents with variable numbers of items like invoices, reports, and listings. + +## Basic Loop Syntax + +``` +{{#foreach ArrayName}} + Content to repeat for each item +{{/foreach}} +``` + +The content between `{{#foreach}}` and `{{/foreach}}` will be repeated once for each item in the array. + +## Simple Lists + +### Looping Through Text Items + +**JSON:** +```json +{ + "Fruits": ["Apple", "Banana", "Cherry", "Date"] +} +``` + +**Template:** +``` +Available Fruits: + +{{#foreach Fruits}} +- {{.}} +{{/foreach}} +``` + +**Output:** +``` +Available Fruits: + +- Apple +- Banana +- Cherry +- Date +``` + +**Note:** `{{.}}` (dot) refers to the current item itself when looping through simple values. + +### Numbered Lists + +**JSON:** +```json +{ + "Steps": [ + "Preheat oven to 350°F", + "Mix dry ingredients", + "Add wet ingredients", + "Bake for 30 minutes" + ] +} +``` + +**Template:** +``` +Instructions: + +{{#foreach Steps}} +{{@index}}. {{.}} +{{/foreach}} +``` + +**Output:** +``` +Instructions: + +0. Preheat oven to 350°F +1. Mix dry ingredients +2. Add wet ingredients +3. Bake for 30 minutes +``` + +**Note:** `{{@index}}` starts at 0. For 1-based numbering, see [Loop Variables](#loop-variables) below. + +## Looping Through Objects + +### Basic Object Lists + +**JSON:** +```json +{ + "Products": [ + { + "Name": "Widget", + "Price": 10.00, + "SKU": "WDG-001" + }, + { + "Name": "Gadget", + "Price": 25.00, + "SKU": "GDG-002" + }, + { + "Name": "Doohickey", + "Price": 15.00, + "SKU": "DHK-003" + } + ] +} +``` + +**Template:** +``` +Product Catalog: + +{{#foreach Products}} +Name: {{Name}} +Price: ${{Price}} +SKU: {{SKU}} +--- +{{/foreach}} +``` + +**Output:** +``` +Product Catalog: + +Name: Widget +Price: $10.00 +SKU: WDG-001 +--- +Name: Gadget +Price: $25.00 +SKU: GDG-002 +--- +Name: Doohickey +Price: $15.00 +SKU: DHK-003 +--- +``` + +### Complex Objects + +**JSON:** +```json +{ + "Employees": [ + { + "Name": "Alice Johnson", + "Title": "Senior Developer", + "Email": "alice@company.com", + "Phone": "+1-555-0100" + }, + { + "Name": "Bob Smith", + "Title": "Product Manager", + "Email": "bob@company.com", + "Phone": "+1-555-0101" + } + ] +} +``` + +**Template:** +``` +EMPLOYEE DIRECTORY + +{{#foreach Employees}} +{{Name}} - {{Title}} + Email: {{Email}} + Phone: {{Phone}} + +{{/foreach}} +``` + +## Table Loops + +One of the most powerful features is repeating table rows: + +### Simple Table + +**Template (create a table in Word):** + +| Product | Price | Stock | +|---------|-------|-------| +| {{#foreach Items}}{{Name}} | ${{Price}} | {{Stock}}{{/foreach}} | + +**JSON:** +```json +{ + "Items": [ + { "Name": "Widget", "Price": 10.00, "Stock": 50 }, + { "Name": "Gadget", "Price": 25.00, "Stock": 30 }, + { "Name": "Doohickey", "Price": 15.00, "Stock": 0 } + ] +} +``` + +**Result:** The table row will be repeated for each item, creating a table with 4 rows total (1 header + 3 data rows). + +### Invoice Line Items + +**Template:** + +| Item | Quantity | Unit Price | Total | +|------|----------|------------|-------| +| {{#foreach LineItems}}{{Description}} | {{Quantity}} | ${{UnitPrice}} | ${{Total}}{{/foreach}} | + +**JSON:** +```json +{ + "LineItems": [ + { + "Description": "Professional Services", + "Quantity": 10, + "UnitPrice": 150.00, + "Total": 1500.00 + }, + { + "Description": "Software License", + "Quantity": 1, + "UnitPrice": 500.00, + "Total": 500.00 + }, + { + "Description": "Support Package", + "Quantity": 12, + "UnitPrice": 50.00, + "Total": 600.00 + } + ] +} +``` + +## Loop Variables + +Special variables are available inside loops: + +### `{{@index}}` - Current Index + +Zero-based index of the current item: + +**Template:** +``` +{{#foreach Items}} +Item #{{@index}}: {{Name}} +{{/foreach}} +``` + +**Output:** +``` +Item #0: Widget +Item #1: Gadget +Item #2: Doohickey +``` + +**For 1-based numbering, you'll need to adjust in your JSON or just add 1 mentally when reading.** + +### `{{@first}}` - First Item + +True for the first iteration only: + +**JSON:** +```json +{ + "Chapters": [ + { "Title": "Introduction", "Pages": 10 }, + { "Title": "Getting Started", "Pages": 25 }, + { "Title": "Advanced Topics", "Pages": 40 } + ] +} +``` + +**Template:** +``` +{{#foreach Chapters}} +{{#if @first}} +=== FIRST CHAPTER === +{{/if}} +Chapter: {{Title}} ({{Pages}} pages) +{{/foreach}} +``` + +**Output:** +``` +=== FIRST CHAPTER === +Chapter: Introduction (10 pages) +Chapter: Getting Started (25 pages) +Chapter: Advanced Topics (40 pages) +``` + +### `{{@last}}` - Last Item + +True for the last iteration only: + +**Template:** +``` +{{#foreach Tags}}{{.}}{{#if not @last}}, {{/if}}{{/foreach}} +``` + +**JSON:** +```json +{ + "Tags": ["JavaScript", "Python", "Java", "C#"] +} +``` + +**Output:** +``` +JavaScript, Python, Java, C# +``` + +(Notice no comma after the last item!) + +### `{{@count}}` - Total Count + +Total number of items in the loop: + +**Template:** +``` +Processing {{@count}} orders: + +{{#foreach Orders}} +Order {{@index}} of {{@count}}: {{OrderNumber}} +{{/foreach}} +``` + +**JSON:** +```json +{ + "Orders": [ + { "OrderNumber": "ORD-001" }, + { "OrderNumber": "ORD-002" }, + { "OrderNumber": "ORD-003" } + ] +} +``` + +**Output:** +``` +Processing 3 orders: + +Order 0 of 3: ORD-001 +Order 1 of 3: ORD-002 +Order 2 of 3: ORD-003 +``` + +## Nested Loops + +You can nest loops within each other for complex data structures: + +### Two Levels + +**JSON:** +```json +{ + "Departments": [ + { + "Name": "Engineering", + "Employees": [ + { "Name": "Alice", "Role": "Senior Dev" }, + { "Name": "Bob", "Role": "Junior Dev" } + ] + }, + { + "Name": "Sales", + "Employees": [ + { "Name": "Charlie", "Role": "Account Manager" }, + { "Name": "Diana", "Role": "Sales Rep" } + ] + } + ] +} +``` + +**Template:** +``` +{{#foreach Departments}} +Department: {{Name}} +{{#foreach Employees}} + - {{Name}} ({{Role}}) +{{/foreach}} + +{{/foreach}} +``` + +**Output:** +``` +Department: Engineering + - Alice (Senior Dev) + - Bob (Junior Dev) + +Department: Sales + - Charlie (Account Manager) + - Diana (Sales Rep) +``` + +### Three Levels + +**JSON:** +```json +{ + "Regions": [ + { + "Name": "North America", + "Countries": [ + { + "Name": "USA", + "Cities": ["New York", "Los Angeles", "Chicago"] + }, + { + "Name": "Canada", + "Cities": ["Toronto", "Vancouver", "Montreal"] + } + ] + }, + { + "Name": "Europe", + "Countries": [ + { + "Name": "UK", + "Cities": ["London", "Manchester"] + } + ] + } + ] +} +``` + +**Template:** +``` +{{#foreach Regions}} +Region: {{Name}} +{{#foreach Countries}} + Country: {{Name}} +{{#foreach Cities}} + - {{.}} +{{/foreach}} +{{/foreach}} + +{{/foreach}} +``` + +## Conditionals in Loops + +Combine loops with conditionals for powerful templates: + +### Conditional Content + +**JSON:** +```json +{ + "Products": [ + { "Name": "Widget", "Price": 10, "InStock": true }, + { "Name": "Gadget", "Price": 25, "InStock": false }, + { "Name": "Doohickey", "Price": 15, "InStock": true } + ] +} +``` + +**Template:** +``` +Product List: + +{{#foreach Products}} +{{Name}} - ${{Price}} +{{#if InStock}} + ✓ Available now +{{else}} + ❌ Out of stock +{{/if}} + +{{/foreach}} +``` + +### Filtering with Conditionals + +**JSON:** +```json +{ + "Orders": [ + { "Id": "001", "Status": "Completed", "Total": 100 }, + { "Id": "002", "Status": "Pending", "Total": 50 }, + { "Id": "003", "Status": "Completed", "Total": 200 }, + { "Id": "004", "Status": "Cancelled", "Total": 75 } + ] +} +``` + +**Template:** +``` +Completed Orders: + +{{#foreach Orders}} +{{#if Status = "Completed"}} +Order {{Id}}: ${{Total}} +{{/if}} +{{/foreach}} +``` + +**Output:** +``` +Completed Orders: + +Order 001: $100 +Order 003: $200 +``` + +## Accessing Parent Context + +When inside a nested loop, you can still access variables from the parent context: + +**JSON:** +```json +{ + "CompanyName": "Acme Corp", + "Departments": [ + { + "Name": "Engineering", + "Employees": [ + { "Name": "Alice" }, + { "Name": "Bob" } + ] + } + ] +} +``` + +**Template:** +``` +{{#foreach Departments}} +{{CompanyName}} - {{Name}} Department +{{#foreach Employees}} + Employee: {{Name}} + Company: {{CompanyName}} +{{/foreach}} +{{/foreach}} +``` + +**Note:** `{{CompanyName}}` is accessible inside nested loops because it's in the parent context. + +## Empty Arrays + +What happens when an array is empty? + +**JSON:** +```json +{ + "Items": [] +} +``` + +**Template:** +``` +Items: +{{#foreach Items}} +- {{Name}} +{{/foreach}} +(End of list) +``` + +**Output:** +``` +Items: +(End of list) +``` + +The loop body simply doesn't execute when the array is empty. + +### Handling Empty Arrays + +**Template:** +``` +{{#if Items}} +Items: +{{#foreach Items}} +- {{Name}} +{{/foreach}} +{{else}} +No items available. +{{/if}} +``` + +## Common Patterns + +### Comma-Separated List + +**JSON:** +```json +{ + "Authors": ["Alice Johnson", "Bob Smith", "Charlie Brown"] +} +``` + +**Template:** +``` +Authors: {{#foreach Authors}}{{.}}{{#if not @last}}, {{/if}}{{/foreach}} +``` + +**Output:** +``` +Authors: Alice Johnson, Bob Smith, Charlie Brown +``` + +### Bulleted List + +**Template:** +``` +Key Features: +{{#foreach Features}} +• {{.}} +{{/foreach}} +``` + +### Numbered List (1-based) + +Since `{{@index}}` is zero-based, here's a workaround for 1-based numbering: + +**JSON:** +```json +{ + "Tasks": [ + { "Number": 1, "Task": "First task" }, + { "Number": 2, "Task": "Second task" }, + { "Number": 3, "Task": "Third task" } + ] +} +``` + +**Template:** +``` +{{#foreach Tasks}} +{{Number}}. {{Task}} +{{/foreach}} +``` + +Or include a calculated number in your JSON data. + +### Alternating Rows + +**JSON:** +```json +{ + "Items": [ + { "Name": "Item 1" }, + { "Name": "Item 2" }, + { "Name": "Item 3" }, + { "Name": "Item 4" } + ] +} +``` + +**Template (in Word with background color):** +``` +{{#foreach Items}} +{{@index}}: {{Name}} +{{/foreach}} +``` + +Then manually apply alternating row colors in Word, or use conditional formatting based on `{{@index}}` if you process the index modulo 2 in your JSON. + +### Section Separators + +**Template:** +``` +{{#foreach Sections}} +{{Title}} + +{{Content}} + +{{#if not @last}} +───────────────── +{{/if}} +{{/foreach}} +``` + +This adds a separator between sections but not after the last one. + +## Real-World Examples + +### Invoice + +**JSON:** +```json +{ + "InvoiceNumber": "INV-2024-001", + "InvoiceDate": "2024-01-15", + "Customer": { + "Name": "Acme Corporation", + "Address": "123 Business St, City, State 12345" + }, + "LineItems": [ + { + "Description": "Website Design", + "Quantity": 1, + "Rate": 5000.00, + "Amount": 5000.00 + }, + { + "Description": "Hosting (12 months)", + "Quantity": 12, + "Rate": 50.00, + "Amount": 600.00 + }, + { + "Description": "Domain Registration", + "Quantity": 1, + "Rate": 15.00, + "Amount": 15.00 + } + ], + "Subtotal": 5615.00, + "Tax": 449.20, + "Total": 6064.20 +} +``` + +**Template:** +``` +INVOICE #{{InvoiceNumber}} +Date: {{InvoiceDate}} + +BILL TO: +{{Customer.Name}} +{{Customer.Address}} + +ITEMS: +| Description | Qty | Rate | Amount | +|-------------|-----|------|--------| +{{#foreach LineItems}} +| {{Description}} | {{Quantity}} | ${{Rate}} | ${{Amount}} | +{{/foreach}} + +Subtotal: ${{Subtotal}} +Tax: ${{Tax}} +TOTAL: ${{Total}} +``` + +### Meeting Attendees + +**JSON:** +```json +{ + "MeetingTitle": "Q1 Planning Session", + "MeetingDate": "2024-01-20", + "Attendees": [ + { + "Name": "Alice Johnson", + "Department": "Engineering", + "Role": "Required" + }, + { + "Name": "Bob Smith", + "Department": "Product", + "Role": "Required" + }, + { + "Name": "Charlie Brown", + "Department": "Design", + "Role": "Optional" + } + ] +} +``` + +**Template:** +``` +Meeting: {{MeetingTitle}} +Date: {{MeetingDate}} + +ATTENDEES: + +Required: +{{#foreach Attendees}} +{{#if Role = "Required"}} +- {{Name}} ({{Department}}) +{{/if}} +{{/foreach}} + +Optional: +{{#foreach Attendees}} +{{#if Role = "Optional"}} +- {{Name}} ({{Department}}) +{{/if}} +{{/foreach}} +``` + +### Product Catalog with Categories + +**JSON:** +```json +{ + "Categories": [ + { + "Name": "Electronics", + "Products": [ + { "Name": "Laptop", "Price": 999 }, + { "Name": "Mouse", "Price": 25 } + ] + }, + { + "Name": "Books", + "Products": [ + { "Name": "Learn Python", "Price": 40 }, + { "Name": "Web Design", "Price": 35 } + ] + } + ] +} +``` + +**Template:** +``` +PRODUCT CATALOG + +{{#foreach Categories}} +━━━━━━━━━━━━━━━━━━━━ +{{Name}} +━━━━━━━━━━━━━━━━━━━━ + +{{#foreach Products}} +• {{Name}} - ${{Price}} +{{/foreach}} + +{{/foreach}} +``` + +## Troubleshooting + +### Loop Not Repeating + +**Check:** +1. JSON has an array: `"Items": [...]` not `"Items": {...}` +2. Array name matches exactly (case-sensitive) +3. Closing tag is present: `{{/foreach}}` + +### Wrong Data Appears + +**Check:** +1. Property names inside loop match the object structure +2. Not confusing parent and nested properties + +**JSON:** +```json +{ + "Products": [ + { "ProductName": "Widget" } + ] +} +``` + +**Template:** +``` +{{#foreach Products}} +{{Name}} ← Wrong! Should be {{ProductName}} +{{ProductName}} ← Correct! +{{/foreach}} +``` + +### Nested Loops Not Working + +Make sure closing tags are in the right order: + +**❌ Wrong:** +``` +{{#foreach A}} + {{#foreach B}} + {{/foreach}} +{{/foreach}} ← Closed A, should close B first +``` + +**✅ Correct:** +``` +{{#foreach A}} + {{#foreach B}} + {{/foreach}} ← Close B +{{/foreach}} ← Close A +``` + +## Best Practices + +1. **Structure JSON to match template needs** - Organize data how you'll display it +2. **Use meaningful property names** - `ProductName` not `N1` +3. **Keep nesting reasonable** - 2-3 levels maximum for readability +4. **Test with edge cases** - Empty arrays, single items, many items +5. **Use loop variables** - `{{@first}}`, `{{@last}}` for special formatting +6. **Combine with conditionals** - Filter or format items based on properties +7. **Add separators carefully** - Use `{{#if not @last}}` to avoid trailing separators + +## Next Steps + +- **[Conditionals Guide](conditionals.md)** - Combining loops with if/else logic +- **[Placeholders Guide](placeholders.md)** - Accessing nested properties in loops +- **[Template Syntax Reference](template-syntax.md)** - Complete syntax guide +- **[Examples Gallery](examples-gallery.md)** - Real-world loop examples + +## Related Topics + +- [Format Specifiers](format-specifiers.md) - Format values inside loops +- [Best Practices](best-practices.md) - Tips for maintainable templates +- [JSON Basics](json-basics.md) - Understanding arrays and objects diff --git a/docs/for-template-authors/placeholders.md b/docs/for-template-authors/placeholders.md new file mode 100644 index 0000000..1168c75 --- /dev/null +++ b/docs/for-template-authors/placeholders.md @@ -0,0 +1,770 @@ +# Placeholders Guide + +Placeholders are the foundation of Templify templates. They mark where data from your JSON file should be inserted into the document. + +## Basic Placeholder Syntax + +A placeholder consists of: +1. Opening double curly braces: `{{` +2. The variable name +3. Closing double curly braces: `}}` + +``` +{{VariableName}} +``` + +**Important rules:** +- Use exactly **two** curly braces on each side +- No spaces inside the braces: `{{Name}}` not `{{ Name }}` +- Names are **case-sensitive**: `{{Name}}` ≠ `{{name}}` + +## Simple Placeholders + +### Basic Example + +**JSON (data.json):** +```json +{ + "CompanyName": "Acme Corporation", + "Year": 2024, + "IsActive": true +} +``` + +**Template:** +``` +Company: {{CompanyName}} +Year: {{Year}} +Active: {{IsActive}} +``` + +**Output:** +``` +Company: Acme Corporation +Year: 2024 +Active: true +``` + +### Text Replacement + +Any text value from JSON is inserted as-is: + +**JSON:** +```json +{ + "CustomerName": "Alice Johnson", + "Email": "alice@example.com", + "PhoneNumber": "+1-555-0123" +} +``` + +**Template:** +``` +Customer: {{CustomerName}} +Contact: {{Email}} or {{PhoneNumber}} +``` + +### Numbers + +Numbers are converted to text automatically: + +**JSON:** +```json +{ + "Quantity": 5, + "Price": 19.99, + "Discount": 0.15, + "Total": 84.96 +} +``` + +**Template:** +``` +Quantity: {{Quantity}} +Price: ${{Price}} +Discount: {{Discount}} +Total: ${{Total}} +``` + +**Output:** +``` +Quantity: 5 +Price: $19.99 +Discount: 0.15 +Total: $84.96 +``` + +### Boolean Values + +True/false values are shown as "true" or "false": + +**JSON:** +```json +{ + "IsVIP": true, + "HasDiscount": false +} +``` + +**Template:** +``` +VIP Status: {{IsVIP}} +Has Discount: {{HasDiscount}} +``` + +**Output:** +``` +VIP Status: true +Has Discount: false +``` + +**Tip:** Use format specifiers for better display (see [Format Specifiers](format-specifiers.md)): +- `{{IsVIP:yesno}}` → "Yes" +- `{{HasDiscount:checkbox}}` → "☐" + +## Nested Properties + +Use dot notation (`.`) to access nested data structures: + +### Two Levels Deep + +**JSON:** +```json +{ + "Customer": { + "Name": "Bob Smith", + "Email": "bob@example.com" + } +} +``` + +**Template:** +``` +Customer Name: {{Customer.Name}} +Email: {{Customer.Email}} +``` + +### Multiple Levels Deep + +**JSON:** +```json +{ + "Company": { + "Name": "Acme Corp", + "Address": { + "Street": "123 Main St", + "City": "Springfield", + "State": "IL", + "PostalCode": { + "Zip": "62701", + "Plus4": "1234" + } + } + } +} +``` + +**Template:** +``` +Company: {{Company.Name}} +Address: {{Company.Address.Street}} + {{Company.Address.City}}, {{Company.Address.State}} {{Company.Address.PostalCode.Zip}} +``` + +### Complex Nested Structure + +**JSON:** +```json +{ + "Order": { + "Id": "ORD-12345", + "Customer": { + "Name": "Sarah Connor", + "Contact": { + "Email": "sarah@example.com", + "Phone": { + "Mobile": "+1-555-0199", + "Home": "+1-555-0188" + } + } + }, + "ShippingAddress": { + "Street": "456 Oak Ave", + "City": "Los Angeles" + } + } +} +``` + +**Template:** +``` +Order #{{Order.Id}} for {{Order.Customer.Name}} + +Contact: {{Order.Customer.Contact.Email}} +Mobile: {{Order.Customer.Contact.Phone.Mobile}} + +Ship to: {{Order.ShippingAddress.Street}}, {{Order.ShippingAddress.City}} +``` + +## Array Access + +### Accessing Array Items by Index + +Arrays use zero-based indexing (`[0]` is the first item): + +**JSON:** +```json +{ + "Colors": ["Red", "Green", "Blue", "Yellow"] +} +``` + +**Template:** +``` +First color: {{Colors[0]}} +Second color: {{Colors[1]}} +Fourth color: {{Colors[3]}} +``` + +**Output:** +``` +First color: Red +Second color: Green +Fourth color: Yellow +``` + +### Array of Objects + +Access properties of array items: + +**JSON:** +```json +{ + "Employees": [ + { + "Name": "Alice Johnson", + "Title": "Manager", + "Email": "alice@company.com" + }, + { + "Name": "Bob Smith", + "Title": "Developer", + "Email": "bob@company.com" + } + ] +} +``` + +**Template:** +``` +Manager: {{Employees[0].Name}} ({{Employees[0].Email}}) +Developer: {{Employees[1].Name}} ({{Employees[1].Email}}) +``` + +### Nested Arrays + +**JSON:** +```json +{ + "Departments": [ + { + "Name": "Sales", + "Teams": [ + { "Name": "East Coast", "Size": 5 }, + { "Name": "West Coast", "Size": 7 } + ] + } + ] +} +``` + +**Template:** +``` +Department: {{Departments[0].Name}} +First Team: {{Departments[0].Teams[0].Name}} ({{Departments[0].Teams[0].Size}} members) +Second Team: {{Departments[0].Teams[1].Name}} ({{Departments[0].Teams[1].Size}} members) +``` + +## Dictionary/Map Access + +Access dictionary values using bracket notation with keys: + +**JSON:** +```json +{ + "Settings": { + "Theme": "Dark", + "Language": "English", + "Timezone": "UTC-5" + } +} +``` + +**Template (two ways to access):** +``` +Theme: {{Settings.Theme}} +Language: {{Settings.Language}} +Timezone: {{Settings.Timezone}} +``` + +Or with brackets (useful for keys with special characters): +``` +Theme: {{Settings[Theme]}} +Language: {{Settings[Language]}} +``` + +## Combining Techniques + +You can combine nested properties and array indexing: + +**JSON:** +```json +{ + "Company": { + "Departments": [ + { + "Name": "Engineering", + "Manager": { + "Name": "Dr. Sarah Chen", + "Email": "sarah.chen@company.com", + "ContactNumbers": [ + "+1-555-0100", + "+1-555-0101" + ] + } + }, + { + "Name": "Sales", + "Manager": { + "Name": "Mike Rodriguez", + "Email": "mike.r@company.com", + "ContactNumbers": [ + "+1-555-0200" + ] + } + } + ] + } +} +``` + +**Template:** +``` +Engineering Manager: {{Company.Departments[0].Manager.Name}} +Email: {{Company.Departments[0].Manager.Email}} +Primary Phone: {{Company.Departments[0].Manager.ContactNumbers[0]}} +Secondary Phone: {{Company.Departments[0].Manager.ContactNumbers[1]}} + +Sales Manager: {{Company.Departments[1].Manager.Name}} +Email: {{Company.Departments[1].Manager.Email}} +Phone: {{Company.Departments[1].Manager.ContactNumbers[0]}} +``` + +## Special Considerations + +### Missing Data + +If a placeholder refers to data that doesn't exist in your JSON: + +**JSON:** +```json +{ + "FirstName": "Alice" +} +``` + +**Template:** +``` +Name: {{FirstName}} {{LastName}} +``` + +**Output (default behavior):** +``` +Name: Alice {{LastName}} +``` + +The placeholder remains unchanged if the data is missing. This helps you spot missing data easily. + +### Null Values + +If a value is explicitly null in JSON: + +**JSON:** +```json +{ + "Name": "Alice", + "MiddleName": null +} +``` + +**Template:** +``` +Full Name: {{Name}} {{MiddleName}} +``` + +The null value is treated as empty text. + +### Empty Strings + +**JSON:** +```json +{ + "Name": "Alice", + "MiddleName": "" +} +``` + +Empty strings are replaced with nothing (empty text). + +### Case Sensitivity + +Remember that placeholder names are case-sensitive: + +**JSON:** +```json +{ + "customerName": "Alice", + "CustomerName": "Bob" +} +``` + +**Template:** +``` +{{customerName}} → Alice +{{CustomerName}} → Bob +{{customername}} → {{customername}} (not found!) +``` + +## Formatting Placeholders + +You can apply formatting to placeholders using format specifiers: + +### Basic Syntax + +``` +{{VariableName:FormatSpecifier}} +``` + +### Common Examples + +**JSON:** +```json +{ + "Name": "alice johnson", + "Price": 1234.567, + "IsActive": true, + "OrderDate": "2024-01-15" +} +``` + +**Template:** +``` +Name: {{Name:uppercase}} +Price: {{Price:currency}} +Active: {{IsActive:yesno}} +Date: {{OrderDate:date:MMMM d, yyyy}} +``` + +**Output:** +``` +Name: ALICE JOHNSON +Price: $1,234.57 +Active: Yes +Date: January 15, 2024 +``` + +For complete formatting options, see [Format Specifiers Guide](format-specifiers.md). + +## Markdown Formatting in Data + +You can include markdown formatting in your JSON data values: + +**JSON:** +```json +{ + "Message": "Hello **Alice**, welcome to *our platform*!", + "Warning": "This is ~~old~~ information.", + "Emphasis": "This is ***very important***!" +} +``` + +**Template:** +``` +{{Message}} +{{Warning}} +{{Emphasis}} +``` + +**Output (with formatting applied):** +``` +Hello Alice, welcome to our platform! +This is old information. +This is very important! +``` + +The markdown syntax (`**bold**`, `*italic*`, `~~strikethrough~~`) is converted to actual formatting in the Word document. + +## Whitespace Handling + +### Spaces Around Placeholders + +Spaces around placeholders are preserved: + +**Template:** +``` +Hello {{Name}} , welcome! +``` + +**Output:** +``` +Hello Alice , welcome! +``` + +Note the space before the comma. Be mindful of spacing! + +**Better:** +``` +Hello {{Name}}, welcome! +``` + +### Line Breaks + +Placeholders can appear anywhere in your text: + +**Template:** +``` +Hello {{Name}}, + +Thank you for your order #{{OrderNumber}}. + +We'll ship to: +{{Address.Street}} +{{Address.City}}, {{Address.State}} {{Address.Zip}} +``` + +All line breaks and formatting are preserved. + +## Best Practices + +### 1. Use Descriptive Names + +**❌ Bad:** +```json +{ + "n": "Alice", + "e": "alice@example.com", + "p": "+1-555-0123" +} +``` + +**✅ Good:** +```json +{ + "CustomerName": "Alice", + "Email": "alice@example.com", + "PhoneNumber": "+1-555-0123" +} +``` + +### 2. Group Related Data + +**❌ Flat:** +```json +{ + "CustomerName": "Alice", + "CustomerEmail": "alice@example.com", + "CustomerCity": "New York", + "CustomerState": "NY" +} +``` + +**✅ Nested:** +```json +{ + "Customer": { + "Name": "Alice", + "Email": "alice@example.com", + "Address": { + "City": "New York", + "State": "NY" + } + } +} +``` + +### 3. Match JSON Structure to Template Logic + +Structure your JSON to match how you'll use it in templates: + +**Template:** +``` +Bill To: {{BillingAddress.Name}} + {{BillingAddress.Street}} + {{BillingAddress.City}} + +Ship To: {{ShippingAddress.Name}} + {{ShippingAddress.Street}} + {{ShippingAddress.City}} +``` + +**JSON:** +```json +{ + "BillingAddress": { + "Name": "Alice Johnson", + "Street": "123 Main St", + "City": "New York" + }, + "ShippingAddress": { + "Name": "Bob Smith", + "Street": "456 Oak Ave", + "City": "Los Angeles" + } +} +``` + +### 4. Test with Sample Data First + +Start with simple test data to verify your placeholders work: + +**Test JSON:** +```json +{ + "Name": "TEST", + "Email": "TEST@EMAIL.COM" +} +``` + +This makes it obvious if placeholders are working. + +### 5. Use Format Specifiers for Better Output + +Instead of raw boolean values: +``` +Status: {{IsActive}} → Status: true +``` + +Use format specifiers: +``` +Status: {{IsActive:yesno}} → Status: Yes +``` + +## Troubleshooting + +### Placeholder Not Replaced + +**Check:** +1. Exact spelling (case-sensitive): `{{Name}}` vs `{{name}}` +2. Proper syntax: `{{Name}}` not `{Name}` or `{{ Name }}` +3. JSON has the key: `"Name": "..."` +4. JSON is valid (use jsonlint.com) + +### Wrong Value Appears + +**Check:** +1. JSON path is correct: `{{Customer.Name}}` needs `{ "Customer": { "Name": "..." } }` +2. Array index is correct: `{{Items[0]}}` (zero-based) +3. No duplicate keys in JSON + +### Formatting Not Applied + +**Check:** +1. Format specifier syntax: `{{Value:format}}` not `{{Value format}}` +2. Format specifier name is correct (see [Format Specifiers](format-specifiers.md)) + +## Real-World Examples + +### Invoice Header + +**JSON:** +```json +{ + "Invoice": { + "Number": "INV-2024-001", + "Date": "2024-01-15", + "DueDate": "2024-02-15" + }, + "Customer": { + "Name": "Acme Corporation", + "Contact": "John Doe", + "Email": "john@acme.com", + "Address": { + "Street": "789 Business Blvd", + "City": "Chicago", + "State": "IL", + "Zip": "60601" + } + } +} +``` + +**Template:** +``` +INVOICE #{{Invoice.Number}} +Date: {{Invoice.Date}} +Due Date: {{Invoice.DueDate}} + +BILL TO: +{{Customer.Name}} +Attn: {{Customer.Contact}} +{{Customer.Address.Street}} +{{Customer.Address.City}}, {{Customer.Address.State}} {{Customer.Address.Zip}} + +Contact: {{Customer.Email}} +``` + +### Certificate Template + +**JSON:** +```json +{ + "Recipient": { + "Name": "Jane Smith", + "Title": "Senior Developer" + }, + "Course": { + "Name": "Advanced Software Architecture", + "CompletionDate": "2024-01-20", + "Score": 95, + "Hours": 40 + }, + "Instructor": { + "Name": "Dr. Robert Johnson", + "Credentials": "PhD, Senior Architect" + } +} +``` + +**Template:** +``` +CERTIFICATE OF COMPLETION + +This certifies that + +{{Recipient.Name}} +{{Recipient.Title}} + +has successfully completed + +{{Course.Name}} + +Date: {{Course.CompletionDate}} +Score: {{Course.Score}}% +Hours: {{Course.Hours}} + +Instructor: {{Instructor.Name}}, {{Instructor.Credentials}} +``` + +## Next Steps + +- **[Conditionals Guide](conditionals.md)** - Show/hide content based on data +- **[Loops Guide](loops.md)** - Repeat content for arrays +- **[Format Specifiers](format-specifiers.md)** - Format numbers, dates, and more +- **[Template Syntax Reference](template-syntax.md)** - Complete syntax guide +- **[Examples Gallery](examples-gallery.md)** - Real-world templates + +## Related Topics + +- [JSON Basics](json-basics.md) - Understanding JSON structure +- [Boolean Expressions](boolean-expressions.md) - Using placeholders in conditions +- [Best Practices](best-practices.md) - Tips for effective templates diff --git a/docs/for-template-authors/template-syntax.md b/docs/for-template-authors/template-syntax.md new file mode 100644 index 0000000..0479d59 --- /dev/null +++ b/docs/for-template-authors/template-syntax.md @@ -0,0 +1,660 @@ +# Template Syntax Reference + +This is a complete reference guide for Templify's template syntax. Use this as a quick lookup when creating templates. + +## Table of Contents + +- [Placeholders](#placeholders) +- [Conditionals](#conditionals) +- [Loops](#loops) +- [Operators](#operators) +- [Format Specifiers](#format-specifiers) +- [Loop Variables](#loop-variables) +- [Markdown Formatting](#markdown-formatting) + +--- + +## Placeholders + +Placeholders are replaced with data from your JSON file. They're wrapped in double curly braces: `{{...}}` + +### Simple Placeholder + +``` +{{VariableName}} +``` + +**JSON:** +```json +{ + "CustomerName": "Alice Johnson" +} +``` + +**Template:** +``` +Customer: {{CustomerName}} +``` + +**Output:** +``` +Customer: Alice Johnson +``` + +### Nested Properties (Dot Notation) + +Access nested data using dots (`.`): + +``` +{{Parent.Child.Property}} +``` + +**JSON:** +```json +{ + "Customer": { + "Name": "Bob Smith", + "Address": { + "City": "New York", + "Zip": "10001" + } + } +} +``` + +**Template:** +``` +Name: {{Customer.Name}} +City: {{Customer.Address.City}} +ZIP: {{Customer.Address.Zip}} +``` + +### Array Indexing + +Access specific array items by index (starting at 0): + +``` +{{ArrayName[Index]}} +{{ArrayName[0].Property}} +``` + +**JSON:** +```json +{ + "Colors": ["Red", "Green", "Blue"], + "Users": [ + { "Name": "Alice", "Age": 25 }, + { "Name": "Bob", "Age": 30 } + ] +} +``` + +**Template:** +``` +First color: {{Colors[0]}} +Second user: {{Users[1].Name}}, age {{Users[1].Age}} +``` + +### Case Sensitivity + +Placeholder names are **case-sensitive**. `{{Name}}` and `{{name}}` are different. + +--- + +## Conditionals + +Conditionals let you show or hide content based on data values. + +### Basic Conditional + +``` +{{#if VariableName}} + Content to show if true +{{/if}} +``` + +**JSON:** +```json +{ + "IsVIP": true +} +``` + +**Template:** +``` +{{#if IsVIP}} +Thank you for being a VIP member! +{{/if}} +``` + +### Conditional with Else + +``` +{{#if Condition}} + Content when true +{{else}} + Content when false +{{/if}} +``` + +**JSON:** +```json +{ + "Status": "Active" +} +``` + +**Template:** +``` +{{#if Status = "Active"}} +Your account is active. +{{else}} +Your account is inactive. +{{/if}} +``` + +### Conditional with Comparisons + +``` +{{#if Variable operator Value}} + Content +{{/if}} +``` + +**Template:** +``` +{{#if Age >= 18}} +You are an adult. +{{/if}} + +{{#if Score > 90}} +Excellent! +{{else}} +Keep trying! +{{/if}} +``` + +### Multiple Conditions + +Use `and` or `or` to combine conditions: + +``` +{{#if Condition1 and Condition2}} + Both are true +{{/if}} + +{{#if Condition1 or Condition2}} + At least one is true +{{/if}} +``` + +**JSON:** +```json +{ + "Age": 25, + "HasLicense": true, + "Country": "USA" +} +``` + +**Template:** +``` +{{#if Age >= 18 and HasLicense}} +You can rent a car. +{{/if}} + +{{#if Country = "USA" or Country = "Canada"}} +North American customer +{{/if}} +``` + +### Negation + +Use `not` to negate a condition: + +``` +{{#if not IsExpired}} + Subscription is active +{{/if}} + +{{#if not (Age < 18)}} + You are an adult +{{/if}} +``` + +--- + +## Loops + +Loops repeat content for each item in an array. + +### Basic Loop + +``` +{{#foreach ArrayName}} + Content to repeat +{{/foreach}} +``` + +**JSON:** +```json +{ + "Products": [ + { "Name": "Widget", "Price": 10 }, + { "Name": "Gadget", "Price": 20 } + ] +} +``` + +**Template:** +``` +Product List: + +{{#foreach Products}} +- {{Name}}: ${{Price}} +{{/foreach}} +``` + +**Output:** +``` +Product List: + +- Widget: $10 +- Gadget: $20 +``` + +### Nested Loops + +You can nest loops within each other: + +**JSON:** +```json +{ + "Departments": [ + { + "Name": "Sales", + "Employees": [ + { "Name": "Alice" }, + { "Name": "Bob" } + ] + }, + { + "Name": "Engineering", + "Employees": [ + { "Name": "Charlie" }, + { "Name": "Diana" } + ] + } + ] +} +``` + +**Template:** +``` +{{#foreach Departments}} +Department: {{Name}} +{{#foreach Employees}} + - {{Name}} +{{/foreach}} + +{{/foreach}} +``` + +### Loops in Tables + +Loops can repeat table rows: + +**Template (in Word table):** + +| Product | Price | +|---------|-------| +| {{#foreach Items}}{{Name}} | ${{Price}}{{/foreach}} | + +The row will be repeated for each item. + +--- + +## Operators + +### Comparison Operators + +| Operator | Meaning | Example | +|----------|---------|---------| +| `=` | Equal to | `{{#if Status = "Active"}}` | +| `!=` | Not equal to | `{{#if Status != "Pending"}}` | +| `>` | Greater than | `{{#if Age > 18}}` | +| `<` | Less than | `{{#if Price < 100}}` | +| `>=` | Greater than or equal | `{{#if Score >= 90}}` | +| `<=` | Less than or equal | `{{#if Stock <= 10}}` | + +### Logical Operators + +| Operator | Meaning | Example | +|----------|---------|---------| +| `and` | Both conditions true | `{{#if Age >= 18 and HasLicense}}` | +| `or` | At least one true | `{{#if IsVIP or IsPremium}}` | +| `not` | Negates condition | `{{#if not IsExpired}}` | + +### Operator Precedence + +1. Parentheses `()` +2. `not` +3. Comparison operators (`=`, `!=`, `>`, etc.) +4. `and` +5. `or` + +**Example:** +``` +{{#if (Age > 18 or HasParent) and not IsBanned}} + Can enter +{{/if}} +``` + +--- + +## Format Specifiers + +Format specifiers control how values are displayed. Add them after a colon (`:`) in the placeholder. + +### Basic Syntax + +``` +{{VariableName:format}} +``` + +### Common Formats + +| Format | Description | Example Input | Example Output | +|--------|-------------|---------------|----------------| +| `:uppercase` | Convert to UPPERCASE | "hello" | HELLO | +| `:lowercase` | Convert to lowercase | "HELLO" | hello | +| `:yesno` | true/false → Yes/No | true | Yes | +| `:checkbox` | true/false → ☑/☐ | false | ☐ | +| `:number:N2` | Format number with 2 decimals | 1234.5 | 1,234.50 | +| `:currency` | Format as currency | 1234.5 | $1,234.50 | +| `:date:yyyy-MM-dd` | Format date | (date value) | 2024-01-15 | + +### Examples + +**JSON:** +```json +{ + "CustomerName": "alice johnson", + "IsActive": true, + "HasDiscount": false, + "Price": 1234.567, + "OrderDate": "2024-01-15" +} +``` + +**Template:** +``` +Name: {{CustomerName:uppercase}} +Status: {{IsActive:yesno}} +Discount: {{HasDiscount:checkbox}} +Price: {{Price:currency}} +Date: {{OrderDate:date:MMMM d, yyyy}} +``` + +**Output:** +``` +Name: ALICE JOHNSON +Status: Yes +Discount: ☐ +Price: $1,234.57 +Date: January 15, 2024 +``` + +For more format specifier details, see [Format Specifiers Guide](format-specifiers.md). + +--- + +## Loop Variables + +Special variables available inside loops: + +### `{{@index}}` + +The current loop iteration index (starts at 0): + +**Template:** +``` +{{#foreach Items}} +Item {{@index}}: {{Name}} +{{/foreach}} +``` + +**Output:** +``` +Item 0: Widget +Item 1: Gadget +Item 2: Doohickey +``` + +### `{{@first}}` + +True if this is the first iteration: + +**Template:** +``` +{{#foreach Items}} +{{#if @first}} +*** FIRST ITEM *** +{{/if}} +{{Name}} +{{/foreach}} +``` + +### `{{@last}}` + +True if this is the last iteration: + +**Template:** +``` +{{#foreach Items}} +{{Name}}{{#if not @last}}, {{/if}} +{{/foreach}} +``` + +**Output:** +``` +Widget, Gadget, Doohickey +``` + +### `{{@count}}` + +Total number of items in the loop: + +**Template:** +``` +{{#foreach Items}} +Processing item {{@index}} of {{@count}}... +{{/foreach}} +``` + +**Output:** +``` +Processing item 0 of 3... +Processing item 1 of 3... +Processing item 2 of 3... +``` + +--- + +## Markdown Formatting + +Apply formatting to text using markdown syntax in your JSON data: + +### Bold + +```json +{ + "Message": "This is **bold** text" +} +``` + +Or use underscores: +```json +{ + "Message": "This is __bold__ text" +} +``` + +### Italic + +```json +{ + "Message": "This is *italic* text" +} +``` + +Or use underscores: +```json +{ + "Message": "This is _italic_ text" +} +``` + +### Strikethrough + +```json +{ + "Message": "This is ~~strikethrough~~ text" +} +``` + +### Bold + Italic + +```json +{ + "Message": "This is ***bold and italic*** text" +} +``` + +### Combining with Template Formatting + +The markdown formatting is **merged** with the template's formatting. If your template has red text, and you add `**bold**` in the data, the output will be **red bold text**. + +--- + +## Special Characters + +### Literal Curly Braces + +To include literal `{{` or `}}` in your document without creating a placeholder, there's currently no escape mechanism. Best practice: avoid using `{{` in your document text unless it's a placeholder. + +### Whitespace + +Templify preserves whitespace in your templates: + +``` +{{#if IsActive}} + This line is indented +{{/if}} +``` + +Will output with the indentation preserved. + +--- + +## Quick Syntax Summary + +| Feature | Syntax | Example | +|---------|--------|---------| +| Placeholder | `{{Name}}` | `{{CustomerName}}` | +| Nested | `{{Parent.Child}}` | `{{Customer.Address.City}}` | +| Array | `{{Array[0]}}` | `{{Colors[0]}}` | +| If | `{{#if ...}}...{{/if}}` | `{{#if IsActive}}...{{/if}}` | +| If/Else | `{{#if ...}}...{{else}}...{{/if}}` | See above | +| Loop | `{{#foreach ...}}...{{/foreach}}` | `{{#foreach Items}}...{{/foreach}}` | +| Format | `{{Name:format}}` | `{{Price:currency}}` | +| Loop Index | `{{@index}}` | `{{@index}}` | +| Loop First | `{{@first}}` | `{{#if @first}}...{{/if}}` | +| Loop Last | `{{@last}}` | `{{#if @last}}...{{/if}}` | +| Loop Count | `{{@count}}` | `{{@count}}` | + +--- + +## Common Patterns + +### Numbered List + +``` +{{#foreach Items}} +{{@index}}. {{Name}} +{{/foreach}} +``` + +### Comma-Separated List + +``` +{{#foreach Tags}}{{.}}{{#if not @last}}, {{/if}}{{/foreach}} +``` + +### Conditional with Multiple Checks + +``` +{{#if Age >= 18 and HasLicense and not IsSuspended}} +Eligible to drive +{{/if}} +``` + +### Nested Conditionals + +``` +{{#if IsLoggedIn}} + {{#if IsPremium}} + Premium content + {{else}} + Regular content + {{/if}} +{{else}} + Please log in +{{/if}} +``` + +### Table with Conditional Rows + +``` +| Product | Price | Status | +|---------|-------|--------| +{{#foreach Products}} +| {{Name}} | {{Price}} | {{#if InStock}}Available{{else}}Out of Stock{{/if}} | +{{/foreach}} +``` + +--- + +## Best Practices + +1. **Match case exactly** - `{{Name}}` must match `"Name"` in JSON +2. **Test with simple data first** - Start with basic JSON and gradually add complexity +3. **Use meaningful names** - `{{CustomerFirstName}}` is better than `{{N1}}` +4. **Validate JSON** - Use jsonlint.com to check JSON syntax +5. **Comment your templates** - Add notes in Word comments about complex logic +6. **Keep conditionals simple** - Break complex logic into multiple simpler conditionals + +--- + +## Next Steps + +- **[Placeholders Guide](placeholders.md)** - Deep dive into placeholder usage +- **[Conditionals Guide](conditionals.md)** - Detailed conditional examples +- **[Loops Guide](loops.md)** - Advanced loop techniques +- **[Format Specifiers](format-specifiers.md)** - Complete formatting reference +- **[Examples Gallery](examples-gallery.md)** - Real-world template examples + +--- + +## Quick Troubleshooting + +| Problem | Solution | +|---------|----------| +| Placeholder not replaced | Check that JSON key matches exactly (case-sensitive) | +| Conditional not working | Verify operator syntax and value types | +| Loop not repeating | Ensure JSON has an array for the loop variable | +| Formatting not applied | Check format specifier syntax: `{{Value:format}}` | +| Syntax error | Validate JSON at jsonlint.com | +| Missing data | Check for typos in placeholder names | + +For more help, see [Best Practices](best-practices.md) or [FAQ](../FAQ.md). diff --git a/docs/index.md b/docs/index.md index 15871b4..b39ed2c 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,89 +1,120 @@ # Templify Documentation -Welcome to **Templify** - a .NET library for replacing placeholders in Word documents without requiring Microsoft Word. +Welcome to **Templify** - a powerful tool for creating dynamic Word documents from templates with placeholders, conditionals, and loops. + +--- + +## 👥 Choose Your Path + +### 📝 I Create Word Templates + +**I design Word documents and want to add dynamic placeholders.** + +I work with Word documents and need to create templates with placeholders like `{{CustomerName}}` that get filled in with data. I don't need to write code - I just need to know how to structure my templates and data. + +**→ [Get Started as a Template Author](for-template-authors/getting-started.md)** + +**Quick Links:** +- [JSON Basics](for-template-authors/json-basics.md) - Understanding your data format +- [Template Syntax Reference](for-template-authors/template-syntax.md) - Complete syntax guide +- [Examples Gallery](for-template-authors/examples-gallery.md) - Real-world templates +- [Best Practices](for-template-authors/best-practices.md) - Tips for great templates + +--- + +### 💻 I'm a Developer + +**I'm integrating Templify into my .NET application.** + +I'm a software developer who wants to use the Templify library in my C# application to programmatically generate Word documents from templates. + +**→ [Get Started as a Developer](for-developers/quick-start.md)** *(Coming soon)* + +**Quick Links:** +- [Installation Guide](for-developers/installation.md) *(Coming soon)* +- [API Reference](for-developers/api-reference.md) *(Coming soon)* +- [Code Examples](for-developers/examples.md) *(Coming soon)* +- [Architecture Overview](for-developers/architecture.md) *(Coming soon)* + +--- ## What is Templify? -Templify provides a simple, intuitive way to generate Word documents from templates with placeholders (`{{variableName}}`), conditionals, and loops. Perfect for generating invoices, reports, contracts, and any other document-based automation. +Templify lets you create Word document templates with special placeholders that get replaced with actual data. Perfect for generating: + +- **Invoices & Receipts** - Customer invoices with line items +- **Reports** - Formatted reports from database data +- **Contracts** - Contracts with dynamic clauses +- **Letters** - Mail merge for personalized letters +- **Certificates** - Batch-generated certificates ## Key Features -✨ **Simple Placeholder Replacement** - `{{VariableName}}` syntax +✨ **Simple Placeholders** - `{{VariableName}}` syntax 🔁 **Loops** - Repeat sections with `{{#foreach}}...{{/foreach}}` ⚡ **Conditionals** - Dynamic content with `{{#if}}...{{else}}...{{/if}}` -📊 **Table Support** - Loop through table rows with data -🎨 **Formatting Preservation** - Maintains Word document styling -🚀 **No Microsoft Word Required** - Uses Open XML SDK +📊 **Table Support** - Loop through table rows +🎨 **Formatting** - Preserves Word styling and supports markdown +🚀 **No Word Required** - Uses Open XML SDK (template authors still use Word to create templates) ## Quick Example -```csharp -using TriasDev.Templify; +### Template (in Word): -var data = new Dictionary -{ - ["CustomerName"] = "John Doe", - ["InvoiceDate"] = DateTime.Now.ToString("yyyy-MM-dd"), - ["Items"] = new List> - { - new() { ["Product"] = "Service A", ["Price"] = "$100" }, - new() { ["Product"] = "Service B", ["Price"] = "$200" } - } -}; - -var processor = new DocumentTemplateProcessor(); -using var templateStream = File.OpenRead("invoice-template.docx"); -using var outputStream = File.Create("invoice-output.docx"); - -var result = processor.ProcessTemplate(templateStream, outputStream, data); ``` +Invoice for {{CustomerName}} +Date: {{InvoiceDate}} -## Get Started - -### 📚 [Quick Start Guide](quick-start.md) -Install Templify and create your first document in 5 minutes - -### 🎓 [Tutorials](tutorials/) -Step-by-step guides from basics to advanced features - -### 📖 [Feature Guides](guides/) -In-depth guides for specific features and use cases +Items: +{{#foreach Items}} +- {{Product}}: {{Price}} +{{/foreach}} +``` -### ❓ [FAQ](FAQ.md) -Common questions and troubleshooting tips +### Data (JSON): -## Installation +```json +{ + "CustomerName": "John Doe", + "InvoiceDate": "2024-01-15", + "Items": [ + { "Product": "Service A", "Price": "$100" }, + { "Product": "Service B", "Price": "$200" } + ] +} +``` -Install via NuGet Package Manager: +### Output: -```bash -dotnet add package TriasDev.Templify ``` +Invoice for John Doe +Date: 2024-01-15 -Or via Package Manager Console: - -```powershell -Install-Package TriasDev.Templify +Items: +- Service A: $100 +- Service B: $200 ``` -## Use Cases +--- + +## Additional Resources -- **Invoices & Receipts** - Generate customer invoices with line items -- **Reports** - Create formatted reports from database data -- **Contracts** - Generate contracts with dynamic clauses -- **Letters** - Mail merge functionality for letters -- **Certificates** - Batch generate certificates with participant data +### ❓ [FAQ](FAQ.md) +Common questions and troubleshooting tips -## Target Framework +### 🎓 [Tutorials](tutorials/) +Step-by-step guides from basics to advanced features -- **.NET 6.0 or later** - Supports .NET 6.0, 8.0, and 9.0 +### 📖 [Quick Start Guide](quick-start.md) +Create your first document in 5 minutes -## License +## Open Source Templify is open source and licensed under the [MIT License](https://github.com/triasdev/templify/blob/main/LICENSE). -## Support +## Support & Community -- 📖 [Documentation](quick-start.md) +- 📖 [Documentation](for-template-authors/getting-started.md) - 🐛 [Report Issues](https://github.com/triasdev/templify/issues) - 💬 [Discussions](https://github.com/triasdev/templify/discussions) +- 🌟 [Star on GitHub](https://github.com/triasdev/templify) diff --git a/docs/quick-start.md b/docs/quick-start.md index 2a06f01..b13bbae 100644 --- a/docs/quick-start.md +++ b/docs/quick-start.md @@ -303,8 +303,8 @@ processor.ProcessTemplate(templateStream, outputStream, data); ``` 📚 **Learn More:** -- [Format Specifiers Guide](guides/format-specifiers.md) - Complete formatting reference -- [Boolean Expressions Guide](guides/boolean-expressions.md) - Logic evaluation reference +- [Format Specifiers Guide](for-template-authors/format-specifiers.md) - Complete formatting reference +- [Boolean Expressions Guide](for-template-authors/boolean-expressions.md) - Logic evaluation reference --- diff --git a/docs/tutorials/index.md b/docs/tutorials/index.md index 71e090d..f8eecda 100644 --- a/docs/tutorials/index.md +++ b/docs/tutorials/index.md @@ -51,12 +51,12 @@ We recommend following the tutorials in order: After completing the tutorials, explore: -- **[Guides](../guides/)** - In-depth feature documentation +- **[Template Author Guides](../for-template-authors/)** - In-depth feature documentation - **[FAQ](../FAQ.md)** - Common questions and solutions - **[Quick Start](../quick-start.md)** - Quick reference guide ## Need Help? - Check the [FAQ](../FAQ.md) for common issues -- Browse [feature guides](../guides/) for specific topics +- Browse [template author guides](../for-template-authors/) for specific topics - Open an [issue on GitHub](https://github.com/triasdev/templify/issues) if you're stuck diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..7fa08b8 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,121 @@ +# Templify Examples + +This folder contains downloadable example templates and sample data files that you can use to learn Templify. + +## Available Examples + +Each example folder contains: + +- **template.docx** - The Word template with Templify placeholders +- **data.json** - Sample JSON data to fill the template +- **output.docx** - Pre-generated output showing what the result looks like + +## Examples (Coming Soon) + +### hello-world/ +A simple introduction demonstrating basic placeholder replacement. + +**Features:** +- Simple placeholders +- Text replacement +- Beginner-friendly + +### invoice/ +A professional invoice template with line items and calculations. + +**Features:** +- Nested properties (`Customer.Name`, `Customer.Address`) +- Table row loops for line items +- Number formatting +- Multiple sections + +### conditionals/ +Demonstrates conditional sections that show/hide based on data. + +**Features:** +- If/else logic +- Boolean flags +- Status-based content +- Multiple conditionals + +### nested-loops/ +Shows how to work with hierarchical data. + +**Features:** +- Nested loops (departments → employees) +- Multi-level data structures +- Parent context access + +## How to Use These Examples + +### 1. Download Files + +Download both the template and data files from the example folder you want to try. + +### 2. Process the Template + +**Option A: Using Templify GUI** + +1. Open the Templify GUI application +2. Click "Select Template" and choose the `template.docx` file +3. Click "Select Data" and choose the `data.json` file +4. Click "Process Template" +5. Save the output + +**Option B: Using Templify CLI** + +```bash +templify process template.docx --data data.json --output my-output.docx +``` + +**Option C: Using Code (C#)** + +```csharp +using TriasDev.Templify; + +var data = JsonDataParser.ParseJsonFile("data.json"); +var processor = new DocumentTemplateProcessor(); + +using var templateStream = File.OpenRead("template.docx"); +using var outputStream = File.Create("my-output.docx"); + +var result = processor.ProcessTemplate(templateStream, outputStream, data); +``` + +### 3. Compare with Pre-Generated Output + +Open the included `output.docx` file to see what the expected result looks like. + +### 4. Experiment! + +- **Modify the JSON data** - Change values, add items to arrays, etc. +- **Edit the template** - Add new placeholders, change formatting +- **Create variations** - Try different conditional values +- **Learn by doing** - Break things and fix them! + +## Creating Your Own Templates + +After trying these examples: + +1. Start with a simple example (hello-world) +2. Modify it to match your use case +3. Gradually add complexity (conditionals, loops) +4. Refer to the [Template Author Documentation](../docs/for-template-authors/getting-started.md) + +## Tips + +- **Validate JSON** - Use [jsonlint.com](https://jsonlint.com) to check JSON syntax +- **Start simple** - Begin with basic placeholders before adding loops/conditionals +- **Test incrementally** - Make small changes and test frequently +- **Read the guides** - Check [docs/for-template-authors/](../docs/for-template-authors/) for detailed explanations + +## Need Help? + +- **[Template Author Documentation](../docs/for-template-authors/getting-started.md)** - Complete guide +- **[Examples Gallery](../docs/for-template-authors/examples-gallery.md)** - Visual examples +- **[FAQ](../docs/FAQ.md)** - Common questions +- **[GitHub Issues](https://github.com/triasdev/templify/issues)** - Report problems + +--- + +*Examples are automatically generated using the Templify DocumentGenerator tool to ensure accuracy.* diff --git a/mkdocs.yml b/mkdocs.yml index aba4dd9..a79855e 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -67,13 +67,22 @@ extra: nav: - Home: index.md - - Quick Start: quick-start.md + - For Template Authors: + - Getting Started: for-template-authors/getting-started.md + - JSON Basics: for-template-authors/json-basics.md + - Template Syntax: for-template-authors/template-syntax.md + - Placeholders: for-template-authors/placeholders.md + - Conditionals: for-template-authors/conditionals.md + - Loops: for-template-authors/loops.md + - Format Specifiers: for-template-authors/format-specifiers.md + - Boolean Expressions: for-template-authors/boolean-expressions.md + - Best Practices: for-template-authors/best-practices.md + - Examples Gallery: for-template-authors/examples-gallery.md + - For Developers: + - Coming Soon: for-developers/quick-start.md - Tutorials: - tutorials/index.md - Hello World: tutorials/01-hello-world.md - Invoice Generator: tutorials/02-invoice-generator.md - - Guides: - - guides/index.md - - Boolean Expressions: guides/boolean-expressions.md - - Format Specifiers: guides/format-specifiers.md - FAQ: FAQ.md + - Quick Start (Legacy): quick-start.md From d96e29566e84c12a598ff5400e3b9a75520bc6ca Mon Sep 17 00:00:00 2001 From: Vaceslav Ustinov Date: Fri, 21 Nov 2025 23:14:46 +0100 Subject: [PATCH 3/3] fix: correct invalid JSON syntax in conditionals.md Fixed multi-line key name 'Years\n\nExperience' to 'YearsExperience' to match template placeholder and create valid JSON. Addresses Copilot review comment on PR #21. --- docs/for-template-authors/conditionals.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/for-template-authors/conditionals.md b/docs/for-template-authors/conditionals.md index 9dc2be3..513060f 100644 --- a/docs/for-template-authors/conditionals.md +++ b/docs/for-template-authors/conditionals.md @@ -170,9 +170,7 @@ Excellent work! You earned an A grade. **JSON:** ```json { - "Years - -Experience": 5, + "YearsExperience": 5, "MinimumOrder": 100, "OrderAmount": 100 }