<# .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: .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 }