208 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			PowerShell
		
	
	
	
	
	
			
		
		
	
	
			208 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			PowerShell
		
	
	
	
	
	
<#
 | 
						|
.SYNOPSIS
 | 
						|
Décompresse la section ```compressed-json``` d'un fichier .excalidraw.md (Obsidian/Excalidraw).
 | 
						|
Utilise Node.js + npm pour accéder à la librairie LZ-String officielle.
 | 
						|
 | 
						|
.PARAMETER Path
 | 
						|
Chemin du .excalidraw.md
 | 
						|
 | 
						|
.PARAMETER OutFile
 | 
						|
Fichier JSON de sortie (optionnel). Par défaut: <Path>.decompressed.json
 | 
						|
#>
 | 
						|
 | 
						|
param(
 | 
						|
  [Parameter(Mandatory=$true)]
 | 
						|
  [string]$Path,
 | 
						|
  [string]$OutFile
 | 
						|
)
 | 
						|
 | 
						|
if (-not (Test-Path -LiteralPath $Path)) {
 | 
						|
  Write-Host "❌ Fichier introuvable: $Path" -ForegroundColor Red
 | 
						|
  exit 1
 | 
						|
}
 | 
						|
 | 
						|
# --- Lecture et extraction du bloc compressed-json --------------------------
 | 
						|
$content = Get-Content -LiteralPath $Path -Raw
 | 
						|
 | 
						|
$encoded = $null
 | 
						|
if ($content -match '```compressed-json\s*([\s\S]*?)\s*```') {
 | 
						|
  $encoded = $matches[1]
 | 
						|
} elseif ($content -match '%%\s*compressed-json\s*([\s\S]*?)\s*%%') {
 | 
						|
  $encoded = $matches[1]
 | 
						|
} else {
 | 
						|
  Write-Host "⚠️ Aucune section compressed-json trouvée (ni fenced ``` ni %%)." -ForegroundColor Yellow
 | 
						|
  exit 2
 | 
						|
}
 | 
						|
 | 
						|
$encoded = ($encoded -replace '[^A-Za-z0-9\+\/\=]', '').Trim()
 | 
						|
if (-not $encoded) {
 | 
						|
  Write-Host "⚠️ Section compressed-json vide après nettoyage." -ForegroundColor Yellow
 | 
						|
  exit 3
 | 
						|
}
 | 
						|
 | 
						|
if (-not $OutFile) {
 | 
						|
  $OutFile = [System.IO.Path]::ChangeExtension($Path, ".decompressed.json")
 | 
						|
}
 | 
						|
 | 
						|
# --- Vérifier Node.js ---
 | 
						|
$node = Get-Command node -ErrorAction SilentlyContinue
 | 
						|
if (-not $node) {
 | 
						|
  Write-Host "❌ Node.js n'est pas installé ou non trouvé dans PATH." -ForegroundColor Red
 | 
						|
  Write-Host "   Installez Node.js depuis https://nodejs.org/" -ForegroundColor Yellow
 | 
						|
  exit 5
 | 
						|
}
 | 
						|
 | 
						|
# --- Créer un fichier temporaire Node.js ---
 | 
						|
$tempDir = [System.IO.Path]::GetTempPath()
 | 
						|
$tempScript = Join-Path $tempDir "decompress_lz.js"
 | 
						|
$tempOutput = Join-Path $tempDir "lz_output.json"
 | 
						|
 | 
						|
# Créer le script Node.js
 | 
						|
$nodeScript = @"
 | 
						|
// Décompression LZ-String manuelle (compatible avec compressToBase64)
 | 
						|
const keyStrBase64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
 | 
						|
 | 
						|
function decompressFromBase64(input) {
 | 
						|
  if (!input) return "";
 | 
						|
 | 
						|
  // Décodage base64 vers tableau d'octets
 | 
						|
  let bytes = [];
 | 
						|
  for (let i = 0; i < input.length; i += 4) {
 | 
						|
    let b1 = keyStrBase64.indexOf(input[i]) || 0;
 | 
						|
    let b2 = keyStrBase64.indexOf(input[i + 1]) || 0;
 | 
						|
    let b3 = keyStrBase64.indexOf(input[i + 2]) || 0;
 | 
						|
    let b4 = keyStrBase64.indexOf(input[i + 3]) || 0;
 | 
						|
 | 
						|
    bytes.push((b1 << 2) | (b2 >> 4));
 | 
						|
    if (b3 < 64) bytes.push(((b2 & 15) << 4) | (b3 >> 2));
 | 
						|
    if (b4 < 64) bytes.push(((b3 & 3) << 6) | b4);
 | 
						|
  }
 | 
						|
 | 
						|
  // Conversion en UTF16 (caractères)
 | 
						|
  let compressed = "";
 | 
						|
  for (let i = 0; i < bytes.length; i += 2) {
 | 
						|
    let c = bytes[i] | (bytes[i + 1] << 8 || 0);
 | 
						|
    compressed += String.fromCharCode(c);
 | 
						|
  }
 | 
						|
 | 
						|
  return decompressFromUTF16(compressed);
 | 
						|
}
 | 
						|
 | 
						|
function decompressFromUTF16(compressed) {
 | 
						|
  if (!compressed) return "";
 | 
						|
 | 
						|
  let dict = [];
 | 
						|
  let dictSize = 4;
 | 
						|
  let numBits = 3;
 | 
						|
  let dataIdx = 0;
 | 
						|
  let bitPos = 0;
 | 
						|
 | 
						|
  function readBits(n) {
 | 
						|
    let result = 0;
 | 
						|
    for (let i = 0; i < n; i++) {
 | 
						|
      if (bitPos >= compressed.length * 16) return result;
 | 
						|
      let charIdx = Math.floor(bitPos / 16);
 | 
						|
      let bitOffset = bitPos % 16;
 | 
						|
      let bit = (compressed.charCodeAt(charIdx) >> bitOffset) & 1;
 | 
						|
      result |= (bit << i);
 | 
						|
      bitPos++;
 | 
						|
    }
 | 
						|
    return result;
 | 
						|
  }
 | 
						|
 | 
						|
  // Lire le premier code
 | 
						|
  let c = readBits(2);
 | 
						|
  if (c === 0) {
 | 
						|
    let val = readBits(8);
 | 
						|
    dict.push(String.fromCharCode(val));
 | 
						|
  } else if (c === 1) {
 | 
						|
    let val = readBits(16);
 | 
						|
    dict.push(String.fromCharCode(val));
 | 
						|
  } else if (c === 2) {
 | 
						|
    return "";
 | 
						|
  }
 | 
						|
 | 
						|
  let w = dict[dict.length - 1];
 | 
						|
  let result = w;
 | 
						|
 | 
						|
  while (true) {
 | 
						|
    c = readBits(numBits);
 | 
						|
 | 
						|
    if (c === 0) {
 | 
						|
      let val = readBits(8);
 | 
						|
      dict.push(String.fromCharCode(val));
 | 
						|
      c = dict.length - 1;
 | 
						|
    } else if (c === 1) {
 | 
						|
      let val = readBits(16);
 | 
						|
      dict.push(String.fromCharCode(val));
 | 
						|
      c = dict.length - 1;
 | 
						|
    } else if (c === 2) {
 | 
						|
      return result;
 | 
						|
    }
 | 
						|
 | 
						|
    let entry;
 | 
						|
    if (c < dict.length) {
 | 
						|
      entry = dict[c];
 | 
						|
    } else if (c === dict.length) {
 | 
						|
      entry = w + w[0];
 | 
						|
    } else {
 | 
						|
      return null;
 | 
						|
    }
 | 
						|
 | 
						|
    result += entry;
 | 
						|
 | 
						|
    if (dict.length < 65536) {
 | 
						|
      dict.push(w + entry[0]);
 | 
						|
    }
 | 
						|
 | 
						|
    if (dict.length >= (1 << numBits)) {
 | 
						|
      numBits++;
 | 
						|
    }
 | 
						|
 | 
						|
    w = entry;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
try {
 | 
						|
  const input = process.argv[2];
 | 
						|
  const output = decompressFromBase64(input);
 | 
						|
  const fs = require('fs');
 | 
						|
  fs.writeFileSync(process.argv[3], output, 'utf8');
 | 
						|
  console.log('OK');
 | 
						|
} catch (err) {
 | 
						|
  console.error('ERROR: ' + err.message);
 | 
						|
  process.exit(1);
 | 
						|
}
 | 
						|
"@
 | 
						|
 | 
						|
$nodeScript | Out-File -FilePath $tempScript -Encoding utf8
 | 
						|
 | 
						|
try {
 | 
						|
  Write-Host "📦 Tentative de décompression..." -ForegroundColor Cyan
 | 
						|
  Write-Host "   Base64 length: $($encoded.Length) caractères" -ForegroundColor Gray
 | 
						|
  
 | 
						|
  # Exécuter le script Node.js en passant le base64 directement
 | 
						|
  $output = & node $tempScript $encoded $tempOutput 2>&1
 | 
						|
  
 | 
						|
  if ($LASTEXITCODE -ne 0 -or $output -contains "ERROR") {
 | 
						|
    Write-Host "❌ Erreur de décompression: $output" -ForegroundColor Red
 | 
						|
    exit 4
 | 
						|
  }
 | 
						|
  
 | 
						|
  if (Test-Path $tempOutput) {
 | 
						|
    Copy-Item $tempOutput $OutFile -Force
 | 
						|
    $json = Get-Content $tempOutput -Raw
 | 
						|
    Write-Host "✅ Décompression OK → $OutFile" -ForegroundColor Green
 | 
						|
    Write-Host "   Taille JSON: $($json.Length) caractères" -ForegroundColor Gray
 | 
						|
  } else {
 | 
						|
    Write-Host "❌ Le fichier de sortie n'a pas été créé." -ForegroundColor Red
 | 
						|
    exit 4
 | 
						|
  }
 | 
						|
} catch {
 | 
						|
  Write-Host "❌ Erreur lors de l'exécution: $_" -ForegroundColor Red
 | 
						|
  exit 4
 | 
						|
} finally {
 | 
						|
  # Nettoyage
 | 
						|
  Remove-Item $tempScript -ErrorAction SilentlyContinue
 | 
						|
  Remove-Item $tempOutput -ErrorAction SilentlyContinue
 | 
						|
} |