Skip to content

Commit 576cfd2

Browse files
committed
add support for markdown image format #5
1 parent 0884495 commit 576cfd2

5 files changed

Lines changed: 399 additions & 19 deletions

File tree

README.md

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,17 +68,28 @@ A PowerShell library for publishing markdown files authored in markdown to Blogg
6868

6969
## Image Support
7070

71-
PSBlogger supports Google Drive for hosting your images.
71+
PSBlogger supports Google Drive for hosting your images and handles both standard markdown and Obsidian image formats.
72+
73+
### Supported Image Formats
74+
75+
PSBlogger can detect and process images in both formats:
76+
77+
- **Standard Markdown**: `![alt text](image.png "optional title")`
78+
- **Obsidian Format**: `![[image.png|alt text]]`
79+
80+
When publishing, all images are converted to standard markdown format with Google Drive URLs, ensuring compatibility across platforms.
81+
82+
### Google Drive Integration
7283

7384
- Add-GoogleDriveFile: Uploads a file to your Google Drive
7485
- Add-GoogleDriveFolder: Creates a folder in your Google Drive
7586
- Get-GoogleDriveItems: Query the contents of your Google Drive
7687
- Set-GoogleDriveFilePermission: Modifes the permission of a file.
7788

78-
Markdown methods for managing images.
89+
### Markdown Image Management
7990

80-
- Find-MarkdownImages: scans the markdown file to locate images in the content.
81-
- Update-MarkdownImages: updates the content in the markdown file with updated urls
91+
- Find-MarkdownImages: scans the markdown file to locate images in both standard and Obsidian formats
92+
- Update-MarkdownImages: updates the content in the markdown file with updated urls, converting all formats to standard markdown
8293

8394
## Future
8495

src/public/Find-MarkdownImages.ps1

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
Finds all image references in a markdown file.
44
55
.DESCRIPTION
6-
Parses a markdown file and extracts all image references (both inline and reference-style).
6+
Parses a markdown file and extracts all image references including:
7+
- Standard markdown format: ![alt text](image_path "optional title")
8+
- Obsidian format: ![[image_path|alt text]]
79
Returns information about each image including the original markdown syntax, image path, alt text, and title.
810
911
.PARAMETER File
@@ -23,17 +25,59 @@ function Find-MarkdownImages {
2325
$content = Get-Content -Path $File -Raw
2426
$images = @()
2527
$fileDirectory = Split-Path -Path $File -Parent
28+
29+
# If the file is in the current directory, use the current directory
30+
if ([string]::IsNullOrEmpty($fileDirectory)) {
31+
$fileDirectory = "."
32+
}
2633

27-
# Regex pattern for inline images: ![alt text](image_path "optional title")
28-
$inlinePattern = '!\[([^\]]*)\]\(([^)]+?)(?:\s+"([^"]*)")?\)'
34+
# Regex pattern for standard markdown images: ![alt text](image_path "optional title")
35+
$standardPattern = '!\[([^\]]*)\]\(([^)]+?)(?:\s+"([^"]*)")?\)'
2936

30-
# Find all inline image matches
31-
$inlineMatches = [regex]::Matches($content, $inlinePattern)
37+
# Regex pattern for Obsidian images: ![[image_path|alt text]]
38+
$obsidianPattern = '!\[\[([^|\]]+?)(?:\|([^\]]*))?\]\]'
3239

33-
foreach ($match in $inlineMatches) {
34-
$altText = $match.Groups[1].Value
35-
$imagePath = $match.Groups[2].Value.Trim()
36-
$title = if ($match.Groups[3].Success) { $match.Groups[3].Value } else { "" }
40+
# Collect all matches with their positions
41+
$allMatches = @()
42+
43+
# Find all standard markdown image matches
44+
$standardMatches = [regex]::Matches($content, $standardPattern)
45+
foreach ($match in $standardMatches) {
46+
$allMatches += @{
47+
Match = $match
48+
Position = $match.Index
49+
Format = "Standard"
50+
}
51+
}
52+
53+
# Find all Obsidian image matches
54+
$obsidianMatches = [regex]::Matches($content, $obsidianPattern)
55+
foreach ($match in $obsidianMatches) {
56+
$allMatches += @{
57+
Match = $match
58+
Position = $match.Index
59+
Format = "Obsidian"
60+
}
61+
}
62+
63+
# Sort matches by position in document
64+
$allMatches = $allMatches | Sort-Object Position
65+
66+
# Process each match in document order
67+
foreach ($matchInfo in $allMatches) {
68+
$match = $matchInfo.Match
69+
$format = $matchInfo.Format
70+
71+
if ($format -eq "Standard") {
72+
$altText = $match.Groups[1].Value
73+
$imagePath = $match.Groups[2].Value.Trim()
74+
$title = if ($match.Groups[3].Success) { $match.Groups[3].Value } else { "" }
75+
} else {
76+
# Obsidian format
77+
$imagePath = $match.Groups[1].Value.Trim()
78+
$altText = if ($match.Groups[2].Success) { $match.Groups[2].Value } else { "" }
79+
$title = "" # Obsidian format doesn't support titles
80+
}
3781

3882
# Skip URLs (images already hosted online)
3983
if ($imagePath -match '^https?://') {

src/public/Update-MarkdownImages.ps1

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
Updates markdown content by replacing local image references with Google Drive URLs.
44
55
.DESCRIPTION
6-
Takes a markdown file and replaces local image references with Google Drive public URLs,
7-
while preserving alt text and titles.
6+
Takes a markdown file and replaces local image references with Google Drive public URLs,
7+
converting all images to standard markdown format while preserving alt text and titles.
88
99
.PARAMETER File
1010
The path to the markdown file to update.
@@ -15,9 +15,10 @@ Each object should have OriginalMarkdown and NewUrl properties.
1515
1616
.EXAMPLE
1717
$mappings = @(
18-
@{ OriginalMarkdown = "![alt](local.jpg)"; NewUrl = "https://drive.google.com/uc?export=view&id=123" }
18+
@{ OriginalMarkdown = "![alt](local.jpg)"; NewUrl = "https://drive.google.com/uc?export=view&id=123"; AltText = "alt"; Title = "" }
19+
@{ OriginalMarkdown = "![[image.png|description]]"; NewUrl = "https://drive.google.com/uc?export=view&id=456"; AltText = "description"; Title = "" }
1920
)
20-
Update-MarkdownImageUrls -File "post.md" -ImageMappings $mappings
21+
Update-MarkdownImages -File "post.md" -ImageMappings $mappings
2122
#>
2223
function Update-MarkdownImages {
2324
[CmdletBinding()]

src/tests/Find-MarkdownImages.Tests.ps1

Lines changed: 181 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ Third image:
148148
}
149149

150150
Context "Filtering and validation" {
151-
It "Should skip HTTP URLs" {
151+
It "Should skip images that are already web hosted (HTTP/HTTPS)" {
152152
$markdownFile = "TestDrive:\with-urls.md"
153153
$markdownContent = @"
154154
![Local image](test-image1.png)
@@ -276,4 +276,184 @@ No images here!
276276
$image.LocalPath | Should -BeLike "*test-image1.png"
277277
}
278278
}
279+
280+
Context "Obsidian image format support" {
281+
282+
BeforeAll {
283+
$obsidianTestCases = @(
284+
@{
285+
Name = "Basic Obsidian format without alt text"
286+
Content = "![[test-image1.png]]"
287+
ExpectedCount = 1
288+
ExpectedAltText = ""
289+
ExpectedFileName = "test-image1.png"
290+
ExpectedOriginal = "![[test-image1.png]]"
291+
},
292+
@{
293+
Name = "Obsidian format with alt text"
294+
Content = "![[test-image1.png|My Alt Text]]"
295+
ExpectedCount = 1
296+
ExpectedAltText = "My Alt Text"
297+
ExpectedFileName = "test-image1.png"
298+
ExpectedOriginal = "![[test-image1.png|My Alt Text]]"
299+
},
300+
@{
301+
Name = "Obsidian format with subfolder path"
302+
Content = "![[subfolder/test-image2.jpg|Image in subfolder]]"
303+
ExpectedCount = 1
304+
ExpectedAltText = "Image in subfolder"
305+
ExpectedFileName = "test-image2.jpg"
306+
ExpectedOriginal = "![[subfolder/test-image2.jpg|Image in subfolder]]"
307+
},
308+
@{
309+
Name = "Obsidian format with empty alt text after pipe"
310+
Content = "![[test-image1.png|]]"
311+
ExpectedCount = 1
312+
ExpectedAltText = ""
313+
ExpectedFileName = "test-image1.png"
314+
ExpectedOriginal = "![[test-image1.png|]]"
315+
},
316+
@{
317+
Name = "Multiple Obsidian images"
318+
Content = @"
319+
![[test-image1.png|First image]]
320+
Some text here
321+
![[subfolder/test-image2.jpg]]
322+
More text
323+
![[test-image1.png|Duplicate image]]
324+
"@
325+
ExpectedCount = 3
326+
ExpectedAltText = @("First image", "", "Duplicate image")
327+
ExpectedFileName = @("test-image1.png", "test-image2.jpg", "test-image1.png")
328+
}
329+
)
330+
}
331+
332+
It "Should handle <Name>" -TestCases $obsidianTestCases {
333+
param($Name, $Content, $ExpectedCount, $ExpectedAltText, $ExpectedFileName, $ExpectedOriginal)
334+
335+
$markdownFile = "TestDrive:\obsidian-test.md"
336+
Set-MarkdownFile $markdownFile $Content
337+
338+
$result = Find-MarkdownImages -File $markdownFile
339+
340+
$result.Count | Should -Be $ExpectedCount
341+
342+
if ($ExpectedCount -eq 1) {
343+
$result[0].AltText | Should -Be $ExpectedAltText
344+
$result[0].FileName | Should -Be $ExpectedFileName
345+
$result[0].Title | Should -Be "" # Obsidian format doesn't support titles
346+
if ($ExpectedOriginal) {
347+
$result[0].OriginalMarkdown | Should -Be $ExpectedOriginal
348+
}
349+
} elseif ($ExpectedCount -gt 1) {
350+
for ($i = 0; $i -lt $ExpectedCount; $i++) {
351+
$result[$i].AltText | Should -Be $ExpectedAltText[$i]
352+
$result[$i].Format | Should -Be $ExpectedFormat[$i]
353+
$result[$i].FileName | Should -Be $ExpectedFileName[$i]
354+
$result[$i].Title | Should -Be ""
355+
}
356+
}
357+
}
358+
359+
It "Should skip Obsidian images with HTTP URLs" {
360+
$markdownFile = "TestDrive:\obsidian-urls.md"
361+
$markdownContent = @"
362+
![[test-image1.png|Local image]]
363+
![[http://example.com/image.jpg|HTTP image]]
364+
![[https://example.com/image.png|HTTPS image]]
365+
"@
366+
Set-MarkdownFile $markdownFile $markdownContent
367+
368+
$result = Find-MarkdownImages -File $markdownFile
369+
370+
$result.Count | Should -Be 1
371+
$result[0].FileName | Should -Be "test-image1.png"
372+
}
373+
374+
It "Should skip non-existent Obsidian images" {
375+
$markdownFile = "TestDrive:\obsidian-missing.md"
376+
$markdownContent = @"
377+
![[test-image1.png|Existing image]]
378+
![[does-not-exist.jpg|Missing image]]
379+
![[subfolder/test-image2.jpg|Another existing]]
380+
"@
381+
Set-MarkdownFile $markdownFile $markdownContent
382+
383+
$result = Find-MarkdownImages -File $markdownFile
384+
385+
$result.Count | Should -Be 2
386+
$result[0].FileName | Should -Be "test-image1.png"
387+
$result[1].FileName | Should -Be "test-image2.jpg"
388+
}
389+
390+
It "Should not find embedded markdown content" {
391+
# arrange
392+
# obsidian can link content from an external file as an embedded markdown block
393+
$markdownFile = "TestDrive:\obsidian-embedded.md"
394+
$markdownContent = "![[Embedded Markdown Page|Title]]"
395+
Set-MarkdownFile $markdownFile $markdownContent
396+
397+
# act
398+
$result = Find-MarkdownImages -File $markdownFile
399+
400+
# assert
401+
$result.Count | Should -Be 0
402+
}
403+
}
404+
405+
Context "Mixed format support (Standard + Obsidian)" {
406+
BeforeAll {
407+
$mixedFormatTestCases = @(
408+
@{
409+
Name = "Both standard and Obsidian in same file"
410+
Content = @"
411+
![Standard image](test-image1.png "Standard title")
412+
![[test-image1.png|Obsidian image]]
413+
![Another standard](subfolder/test-image2.jpg)
414+
![[subfolder/test-image2.jpg]]
415+
"@
416+
ExpectedCount = 4
417+
ExpectedAltTexts = @("Standard image", "Obsidian image", "Another standard", "")
418+
ExpectedTitles = @("Standard title", "", "", "")
419+
},
420+
@{
421+
Name = "Standard format only"
422+
Content = @"
423+
![Image 1](test-image1.png)
424+
![Image 2](subfolder/test-image2.jpg "With title")
425+
"@
426+
ExpectedCount = 2
427+
ExpectedAltTexts = @("Image 1", "Image 2")
428+
ExpectedTitles = @("", "With title")
429+
},
430+
@{
431+
Name = "Obsidian format only"
432+
Content = @"
433+
![[test-image1.png|Alt 1]]
434+
![[subfolder/test-image2.jpg]]
435+
"@
436+
ExpectedCount = 2
437+
ExpectedAltTexts = @("Alt 1", "")
438+
ExpectedTitles = @("", "")
439+
}
440+
)
441+
}
442+
443+
It "Should handle <Name>" -TestCases $mixedFormatTestCases {
444+
param($Name, $Content, $ExpectedCount, $ExpectedAltTexts, $ExpectedTitles)
445+
446+
$markdownFile = "TestDrive:\mixed-format-test.md"
447+
Set-MarkdownFile $markdownFile $Content
448+
449+
$result = Find-MarkdownImages -File $markdownFile
450+
451+
$result.Count | Should -Be $ExpectedCount
452+
453+
for ($i = 0; $i -lt $ExpectedCount; $i++) {
454+
$result[$i].AltText | Should -Be $ExpectedAltTexts[$i]
455+
$result[$i].Title | Should -Be $ExpectedTitles[$i]
456+
}
457+
}
458+
}
279459
}

0 commit comments

Comments
 (0)