Skip to content

Latest commit

 

History

History
649 lines (463 loc) · 14 KB

File metadata and controls

649 lines (463 loc) · 14 KB

Conditional Logic in Legal Markdown

Legal Markdown supports powerful conditional logic using Handlebars-style syntax with enhanced expression evaluation.

Table of Contents

Overview

Legal Markdown supports full conditional expressions with comparison and boolean operators, making it more powerful than Handlebars (which requires helper functions) and on par with Liquid templates.

Key Features:

  • ✅ Comparison operators: ==, !=, >, <, >=, <=
  • ✅ Boolean operators: &&, ||
  • ✅ Automatic inline conversion to helper subexpressions
  • ✅ Nested expressions with parentheses
  • ✅ Multiple value types: strings, numbers, booleans, null
  • ✅ Dot notation for nested objects
  • ✅ Else branches for alternative content

Basic Conditionals

Simple Truthiness Check

Check if a variable exists and is truthy:

---
includeWarranty: true
---
{{#if includeWarranty}}

## Warranty Clause

This product includes a warranty... {{/if}}

Variable Existence

Check if an object property exists:

---
client:
  name: 'Acme Corp'
---
{{#if client.name}} Client: {{client.name}} {{/if}}

Comparison Operators

Legal Markdown supports full comparison expressions. Inline expressions are automatically converted to Handlebars helper subexpressions during processing.

Equality Comparison

Use == for loose equality checks:

---
contract:
  type: 'service'
---
{{#if contract.type == "service"}}

## Service Agreement Terms

This is a service agreement... {{else}}

## Product Sale Terms

This is a product sale... {{/if}}

Numeric Comparisons

Compare numeric values with >, <, >=, <=:

---
contract:
  amount: 50000
  jurisdiction: 'spain'
---
{{#if contract.amount > 10000}} **High Value Contract** - Special terms apply
{{/if}}

{{#if contract.amount >= 100000}} Board approval required {{/if}}

Not Equal

Use != to check inequality:

---
status: 'active'
---
{{#if status != "cancelled"}} This contract is active {{/if}}

This inline syntax is converted internally to: {{#if (neq status "cancelled")}}.

Boolean Operators

Combine multiple conditions using && (AND) and || (OR).

AND Operator

Both conditions must be true:

---
contract:
  amount: 50000
  jurisdiction: 'spain'
  type: 'service'
---
{{#if contract.amount > 10000 && contract.jurisdiction == "spain"}}

## Spanish High-Value Contract Provisions

Special provisions for Spanish contracts over €10,000... {{/if}}

OR Operator

At least one condition must be true:

---
jurisdiction: 'madrid'
---
{{#if jurisdiction == "madrid" || jurisdiction == "barcelona"}} Applicable law:
Spanish Civil Code {{/if}}

Comparison Helpers

Handlebars {{#if}} only checks truthiness. For comparisons, use subexpression helpers:

Helper Usage Description
eq {{#if (eq a b)}} Equal (===)
neq {{#if (neq a b)}} Not equal (!==)
gt {{#if (gt a b)}} Greater than
gte {{#if (gte a b)}} Greater or equal
lt {{#if (lt a b)}} Less than
lte {{#if (lte a b)}} Less or equal
and {{#if (and a b)}} Logical AND
or {{#if (or a b)}} Logical OR
not {{#if (not a)}} Logical NOT

Inline comparison syntax

For convenience, inline comparisons are automatically converted:

{{#if status == "active"}}{{#if (eq status "active")}}
{{#if amount > 1000}}{{#if (gt amount 1000)}}
{{#if a && b}}{{#if (and a b)}}

Complex Expressions

Combine AND/OR with parentheses for grouping:

---
amount: 75000
type: 'service'
vip: false
jurisdiction: 'spain'
---
{{#if (amount > 50000 && type == "service") || vip == true}} VIP service terms
apply {{/if}}

Operator Precedence

  • AND (&&) has higher precedence than OR (||)
  • Use parentheses to control evaluation order
<!-- AND is evaluated first --> {{#if premium && active || trial}} Content

{{/if}}

<!-- Equivalent to: (premium && active) || trial -->

<!-- Use parentheses to change order --> {{#if premium && (active || trial)}}

Content {{/if}}

Value Types

Legal Markdown correctly handles different value types in comparisons:

Strings

Compare strings using quotes:

---
city: 'madrid'
---
{{#if city == "madrid"}} Madrid-specific clause {{/if}}

Numbers

Compare numbers without quotes:

---
amount: 15000
---
{{#if amount > 1000}} High-value contract {{/if}}

{{#if amount >= 10000 && amount < 50000}} Mid-tier pricing applies {{/if}}

Booleans

Use boolean values directly:

---
active: true
premium: false
---
{{#if active == true}} Active account {{/if}}

{{#if active}} Active account {{/if}}

<!-- Same as above -->

{{#if premium == false}} Standard features only {{/if}}

Null/Undefined

Check for null or missing values:

---
description: null
---
{{#if description == null}} No description provided {{/if}}

Else Branches

Provide alternative content when a condition is false:

---
contract:
  type: 'service'
---
{{#if contract.type == "service"}} Service agreement clauses {{else}} Standard
sale clauses {{/if}}

Chained If-Else

While Legal Markdown doesn't have else if, you can nest conditions:

{{#if tier == "premium"}} Premium features {{else}} {{#if tier == "standard"}}
Standard features {{else}} Basic features {{/if}} {{/if}}

Nested Conditionals

Conditions can be nested for complex logic:

---
contract:
  jurisdiction: 'spain'
  amount: 50000
---
{{#if contract.jurisdiction == "spain"}} Spanish law applies

{{#if contract.amount > 10000}} High-value Spanish contract addendum {{/if}}
{{else}} {{#if contract.jurisdiction == "portugal"}} Portuguese law applies
{{/if}} {{/if}}

Unless (Negative Conditional)

Use unless to show content when a condition is false:

---
contract:
  expired: false
---
{{#unless contract.expired}} This contract is still active {{/unless}}

Equivalent to:

{{#if contract.expired == false}} This contract is still active {{/if}}

Array Iteration with Conditionals

Combine conditionals with array iteration:

---
parties:
  - name: 'Acme Corp'
    role: 'client'
    vip: true
  - name: 'Tech Solutions'
    role: 'contractor'
    vip: false
---
{{#parties}}

- **{{name}}** {{#if role == "client"}} (Client - invoicing entity) {{else}}
  (Contractor - service provider) {{/if}} {{#if vip}} **VIP Client** - Priority
  support {{/if}} {{/parties}}

Output:

- **Acme Corp** (Client - invoicing entity) **VIP Client** - Priority support

- **Tech Solutions** (Contractor - service provider)

Best Practices

1. Pre-compute Complex Logic

For very complex conditions, consider pre-computing boolean values in metadata:

---
contract:
  amount: 50000
  jurisdiction: 'spain'

# Pre-computed
isHighValueSpanishContract: true
---
{{#if isHighValueSpanishContract}} Special provisions... {{/if}}

Advantages:

  • More readable templates
  • Easier to test business logic
  • Reusable across multiple conditions

2. Use Descriptive Variable Names

# Good
{{#if requiresBoardApproval}} Executive approval needed {{/if}}

# Less clear
{{#if amount > 100000 && type == "acquisition"}} Executive approval needed
{{/if}}

3. Keep Expressions Readable

Break complex conditions into nested ifs when appropriate:

<!-- Instead of this -->

{{#if type == "service" && (jurisdiction == "spain" || jurisdiction ==
"portugal") && amount > 10000}} Content {{/if}}

<!-- Consider this -->

{{#if type == "service"}} {{#if jurisdiction == "spain" || jurisdiction ==
"portugal"}} {{#if amount > 10000}} Content {{/if}} {{/if}} {{/if}}

4. Comment Complex Conditions

Add comments in YAML or markdown to explain complex logic:

---
# High-value contracts in EU require additional approvals
requiresExecutiveApproval: true # Based on amount > 100000 && region == "EU"
---

5. Test Edge Cases

Test your conditions with various values:

# Test with:
# - Empty strings
# - Zero values
# - Null/undefined
# - Boundary values (e.g., exactly 10000)
amount: 0 # Will this work as expected?
status: '' # Is empty string falsy?

Comparison with Other Template Engines

Legal Markdown has more ergonomic conditional syntax than Handlebars:

Feature Handlebars Liquid Legal Markdown
Simple truthiness {{#if var}} {% if var %} {{#if var}}
Comparisons ❌ Requires {{#if (eq a b)}} {% if a == b %} {{#if a == b}}*
Numeric ❌ Requires {{#if (gt a 10)}} {% if a > 10 %} {{#if a > 10}}*
Boolean logic ❌ Requires {{#if (and a b)}} {% if a and b %} {{#if a && b}}*
Else branches {{else}} {% else %} {{else}}
Unless {{#unless}} {% unless %} {{#unless}}

* Inline expressions are automatically converted to helper subexpressions (eq, gt, and, etc.).

Why this matters:

  • No helper functions needed - Direct comparison syntax is more readable
  • Familiar syntax - Similar to JavaScript, easier for developers
  • Less verbose - Compare {{#if a == b}} vs {{#if (eq a b)}}

Technical Details

Processing Order

Conditional evaluation happens in Phase 3 of the processing pipeline, after variable expansion:

  1. Phase 1: Context Building (parse YAML, merge options)
  2. Phase 2: Variable Expansion ({{variable}} → actual values)
  3. Phase 3: Conditional Evaluation ({{#if}} blocks)
  4. Phase 4: Structure Parsing (headers, cross-references)

This ensures that conditionals evaluate against expanded values, not raw {{variable}} syntax. (Fixes Issue #120)

Implementation

Conditional evaluation happens in src/extensions/template-loops.ts:

Main functions:

  • evaluateCondition() - Entry point, delegates to specialized evaluators
  • evaluateBooleanExpression() - Handles && and || operators
  • evaluateComparisonExpression() - Handles ==, !=, >, <, >=, <=
  • parseComparisonValue() - Parses string/number/boolean literals and variables
  • resolveVariablePath() - Resolves nested object paths (e.g., client.name)

Evaluation logic:

function evaluateCondition(condition: string, metadata: Record<string, any>): boolean {
  // 1. Check for boolean operators
  if (condition.includes('&&') || condition.includes('||')) {
    return evaluateBooleanExpression(condition, metadata);
  }

  // 2. Check for comparison operators
  if (condition.includes('==') || condition.includes('!=') || ...) {
    return evaluateComparisonExpression(condition, metadata);
  }

  // 3. Fallback to truthiness check
  const value = resolveVariablePath(condition, metadata);
  return isTruthy(value);
}

Performance

Expression evaluation adds minimal overhead:

  • Simple variable check: < 0.1ms
  • Comparison expression: < 0.5ms
  • Complex boolean expression: < 1ms

No significant impact on document processing time.

Common Patterns

Legal Document Clauses

---
confidentiality_required: true
jurisdiction: 'California'
contract_value: 75000
client_type: 'enterprise'
---
{{#if confidentiality_required}}

## Confidentiality

All information is confidential. {{/if}}

{{#if contract_value > 50000 && client_type == "enterprise"}}

## Enterprise High-Value Terms

Special provisions for enterprise contracts over $50,000. {{/if}}

{{#if jurisdiction == "California"}} This agreement is governed by California
law. {{/if}}

Service Level Variations

---
service_tier: 'premium'
region: 'US'
active_subscription: true
---
{{#if service_tier == "premium" && active_subscription}}

## Premium Service Features

- 24/7 priority support
- Dedicated account manager
- 99.9% uptime SLA

{{#if region == "US"}}

### US-Specific Benefits

- Toll-free phone support
- Business hours: All US time zones {{/if}} {{/if}}

Contract Pricing Tiers

---
contract:
  amount: 25000
  duration: 12
  region: 'EU'
---
{{#if contract.amount < 10000}} Standard pricing tier {{else}} {{#if
contract.amount >= 10000 && contract.amount < 50000}} Mid-tier pricing {{else}}
Enterprise pricing {{/if}} {{/if}}

{{#if contract.duration >= 12 && contract.amount > 10000}} Annual discount
applied {{/if}}

{{#if contract.region == "EU" && contract.amount > 15000}} VAT calculation
required {{/if}}

Related