Skip to content

Commit b937ea8

Browse files
Use substitution values in image alt and title text (#1163)
* use substitution values in image alt and title text * reorder imports * fix when to resolve subs * add image with subs to subs test * Allow empty alt tag * Add title * Refactor * Update src/Elastic.Markdown/Myst/Directives/DirectiveHtmlRenderer.cs * Update src/Elastic.Markdown/Myst/InlineParsers/DiagnosticLinkInlineParser.cs --------- Co-authored-by: Jan Calanog <jan.calanog@elastic.co>
1 parent 2ffb0de commit b937ea8

File tree

7 files changed

+96
-30
lines changed

7 files changed

+96
-30
lines changed

src/Elastic.Markdown/Myst/Directives/DirectiveHtmlRenderer.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,8 @@ private static void WriteImage(HtmlRenderer renderer, ImageBlock block)
9191
{
9292
Label = block.Label,
9393
Align = block.Align,
94-
Alt = block.Alt,
94+
Alt = block.Alt ?? string.Empty,
95+
Title = block.Title,
9596
Height = block.Height,
9697
Scale = block.Scale,
9798
Target = block.Target,
@@ -128,7 +129,8 @@ private static void WriteFigure(HtmlRenderer renderer, ImageBlock block)
128129
{
129130
Label = block.Label,
130131
Align = block.Align,
131-
Alt = block.Alt,
132+
Alt = block.Alt ?? string.Empty,
133+
Title = block.Title,
132134
Height = block.Height,
133135
Scale = block.Scale,
134136
Target = block.Target,

src/Elastic.Markdown/Myst/Directives/ImageBlock.cs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// See the LICENSE file in the project root for more information
44

55
using Elastic.Markdown.Diagnostics;
6+
using Elastic.Markdown.Helpers;
67
using Elastic.Markdown.IO;
78
using Elastic.Markdown.Myst.InlineParsers;
89

@@ -21,6 +22,11 @@ public class ImageBlock(DirectiveBlockParser parser, ParserContext context)
2122
/// </summary>
2223
public string? Alt { get; set; }
2324

25+
/// <summary>
26+
/// Title text: a short description of the image
27+
/// </summary>
28+
public string? Title { get; set; }
29+
2430
/// <summary>
2531
/// The desired height of the image. Used to reserve space or scale the image vertically. When the “scale” option
2632
/// is also specified, they are combined. For example, a height of 200px and a scale of 50 is equivalent to
@@ -65,9 +71,10 @@ public class ImageBlock(DirectiveBlockParser parser, ParserContext context)
6571
public override void FinalizeAndValidate(ParserContext context)
6672
{
6773
Label = Prop("label", "name");
68-
Alt = Prop("alt");
69-
Align = Prop("align");
74+
Alt = Prop("alt")?.ReplaceSubstitutions(context) ?? string.Empty;
75+
Title = Prop("title")?.ReplaceSubstitutions(context);
7076

77+
Align = Prop("align");
7178
Height = Prop("height", "h");
7279
Width = Prop("width", "w");
7380

@@ -112,5 +119,3 @@ private void ExtractImageUrl(ParserContext context)
112119
}
113120
}
114121
}
115-
116-

src/Elastic.Markdown/Myst/InlineParsers/DiagnosticLinkInlineParser.cs

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -63,38 +63,41 @@ public override bool Match(InlineProcessor processor, ref StringSlice slice)
6363

6464
ValidateAndProcessLink(link, processor, context);
6565

66-
ParseStylingInstructions(link);
66+
ParseStylingInstructions(link, context);
6767

6868
return match;
6969
}
7070

7171

72-
private static void ParseStylingInstructions(LinkInline link)
72+
private static void ParseStylingInstructions(LinkInline link, ParserContext context)
7373
{
7474
if (!link.IsImage)
7575
return;
7676

77-
if (string.IsNullOrWhiteSpace(link.Title) || link.Title.IndexOf('=') < 0)
78-
return;
77+
var attributes = link.GetAttributes();
78+
var title = link.Title;
7979

80-
var matches = LinkRegexExtensions.MatchTitleStylingInstructions().Match(link.Title);
81-
if (!matches.Success)
80+
if (string.IsNullOrEmpty(title))
8281
return;
8382

84-
var width = matches.Groups["width"].Value;
85-
if (!width.EndsWith('%'))
86-
width += "px";
87-
var height = matches.Groups["height"].Value;
88-
if (string.IsNullOrEmpty(height))
89-
height = width;
90-
else if (!height.EndsWith('%'))
91-
height += "px";
92-
var title = link.Title[..matches.Index];
93-
94-
link.Title = title;
95-
var attributes = link.GetAttributes();
96-
attributes.AddProperty("width", width);
97-
attributes.AddProperty("height", height);
83+
var matches = LinkRegexExtensions.MatchTitleStylingInstructions().Match(title);
84+
if (matches.Success)
85+
{
86+
var width = matches.Groups["width"].Value;
87+
if (!width.EndsWith('%'))
88+
width += "px";
89+
var height = matches.Groups["height"].Value;
90+
if (string.IsNullOrEmpty(height))
91+
height = width;
92+
else if (!height.EndsWith('%'))
93+
height += "px";
94+
95+
attributes.AddProperty("width", width);
96+
attributes.AddProperty("height", height);
97+
98+
title = title[..matches.Index];
99+
}
100+
link.Title = title?.ReplaceSubstitutions(context);
98101
}
99102

100103
private static bool IsInCommentBlock(LinkInline link) =>
@@ -183,7 +186,7 @@ private static void ProcessCrossLink(LinkInline link, InlineProcessor processor,
183186
s => processor.EmitError(link, s),
184187
s => processor.EmitWarning(link, s),
185188
uri, out var resolvedUri)
186-
)
189+
)
187190
link.Url = resolvedUri.ToString();
188191
}
189192

src/Elastic.Markdown/Slices/Directives/Image.cshtml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
@using Microsoft.AspNetCore.Mvc.ModelBinding.Validation
12
@inherits RazorSlice<ImageViewModel>
23
<a class="reference internal image-reference" href="javascript:void(0)" onclick="document.getElementById('modal-@Model.UniqueImageId').style.display='flex'">
3-
<img loading="lazy" alt="@Model.Alt" src="@Model.ImageUrl" style="@Model.Style" class="@Model.Screenshot" />
4+
<img loading="lazy" title="@Model.Title" alt="@(Model.Alt == string.Empty ? HtmlString.Empty : new HtmlString(Model.Alt))" src="@Model.ImageUrl" style="@Model.Style" class="@Model.Screenshot" />
45
[CONTENT]
56
</a>
67

@@ -14,7 +15,7 @@
1415
</a>
1516
</span>
1617
<a class="reference internal image-reference" href="@Model.ImageUrl" target="_blank">
17-
<img loading="lazy" alt="@Model.Alt" src="@Model.ImageUrl" />
18+
<img loading="lazy" title="@Model.Title" alt="@(Model.Alt == string.Empty ? HtmlString.Empty : new HtmlString(Model.Alt))" src="@Model.ImageUrl" />
1819
</a>
1920
</div>
2021
</div>

src/Elastic.Markdown/Slices/Directives/_ViewModels.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ public class ImageViewModel
4949
{
5050
public required string? Label { get; init; }
5151
public required string? Align { get; init; }
52-
public required string? Alt { get; init; }
52+
public required string Alt { get; init; }
53+
public required string? Title { get; init; }
5354
public required string? Height { get; init; }
5455
public required string? Scale { get; init; }
5556
public required string? Target { get; init; }

tests/Elastic.Markdown.Tests/Inline/SubstitutionTest.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,3 +165,43 @@ public void OnlySeesGlobalVariable() =>
165165
public void HasNoErrors() => Collector.Diagnostics.Should().HaveCount(0);
166166

167167
}
168+
169+
public class ReplaceInImageAlt(ITestOutputHelper output) : InlineTest(output,
170+
"""
171+
---
172+
sub:
173+
hello-world: Hello World
174+
---
175+
176+
# Testing ReplaceInImageAlt
177+
178+
![{{hello-world}}](_static/img/observability.png)
179+
"""
180+
)
181+
{
182+
183+
[Fact]
184+
public void OnlySeesGlobalVariable() =>
185+
Html.Should().NotContain("alt=\"{{hello-world}}\"")
186+
.And.Contain("alt=\"Hello World\"");
187+
}
188+
189+
public class ReplaceInImageTitle(ITestOutputHelper output) : InlineTest(output,
190+
"""
191+
---
192+
sub:
193+
hello-world: Hello World
194+
---
195+
196+
# Testing ReplaceInImageTitle
197+
198+
![Observability](_static/img/observability.png "{{hello-world}}")
199+
"""
200+
)
201+
{
202+
203+
[Fact]
204+
public void OnlySeesGlobalVariable() =>
205+
Html.Should().NotContain("title=\"{{hello-world}}\"")
206+
.And.Contain("title=\"Hello World\"");
207+
}

tests/authoring/Blocks/ImageBlocks.fs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,17 @@ type ``image ref out of scope`` () =
7979
[<Fact>]
8080
let ``emits an error image reference is outside of documentation scope`` () =
8181
docs |> hasError "./img/observability.png` does not exist. resolved to"
82+
83+
type ``empty alt attribute`` () =
84+
static let markdown = Setup.Markdown """
85+
:::{image} img/some-image.png
86+
:alt:
87+
:width: 250px
88+
:::
89+
"""
90+
91+
[<Fact>]
92+
let ``validate empty alt attribute`` () =
93+
markdown |> convertsToContainingHtml """
94+
<img loading="lazy" alt src="/img/some-image.png" style="width: 250px;">
95+
"""

0 commit comments

Comments
 (0)