Skip to content

Commit f4e2b30

Browse files
authored
Merge pull request #1147 from EamonNerbonne/eamon/BoolHtml
Better support for boolean attributes
2 parents 68b337b + b5c5c94 commit f4e2b30

10 files changed

+4065
-49
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ bin/
66
BenchmarkDotNet.Artifacts
77
*.vspx
88
*.received.txt
9+
.DS_Store

src/ProgressOnderwijsUtils/ApprovalTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ static string ToApprovalPath(SourceLocation sourceLocation)
1111
{
1212
var filename = Path.GetFileNameWithoutExtension(sourceLocation.FilePath);
1313
var filedir = Path.GetDirectoryName(sourceLocation.FilePath);
14-
var approvalPath = $"{filedir}\\{filename}.{sourceLocation.MemberName}.approved.txt";
14+
var approvalPath = Path.Combine(filedir.AssertNotNull(), $"{filename}.{sourceLocation.MemberName}.approved.txt");
1515
return approvalPath;
1616
}
1717

src/ProgressOnderwijsUtils/Html/HtmlSpec.AttributeConstructionMethods.Generated.cs

Lines changed: 177 additions & 0 deletions
Large diffs are not rendered by default.

src/ProgressOnderwijsUtils/Html/HtmlSpec.AttributeLookupTable.Generated.cs

Lines changed: 3779 additions & 0 deletions
Large diffs are not rendered by default.

src/ProgressOnderwijsUtils/Html/HtmlSpec.AttributeNameInterfaces.Generated.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ public interface IHasAttr_shadowrootmode { }
119119
public interface IHasAttr_shadowrootdelegatesfocus { }
120120
public interface IHasAttr_shadowrootclonable { }
121121
public interface IHasAttr_shadowrootserializable { }
122+
public interface IHasAttr_shadowrootcustomelementregistry { }
122123
public interface IHasAttr_cols { }
123124
public interface IHasAttr_rows { }
124125
public interface IHasAttr_wrap { }

src/ProgressOnderwijsUtils/Html/HtmlSpec.HtmlTagKinds.Generated.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2030,7 +2030,7 @@ public struct TD : IHtmlElementAllowingContent<TD>, IHasAttr_colspan, IHasAttr_r
20302030
public static HtmlFragment operator +(TD head, HtmlFragment tail) => HtmlFragment.Fragment(HtmlFragment.Element(head), tail);
20312031
public static HtmlFragment operator +(string head, TD tail) => HtmlFragment.Fragment(head, HtmlFragment.Element(tail));
20322032
}
2033-
public struct TEMPLATE : IHtmlElementAllowingContent<TEMPLATE>, IHasAttr_shadowrootmode, IHasAttr_shadowrootdelegatesfocus, IHasAttr_shadowrootclonable, IHasAttr_shadowrootserializable
2033+
public struct TEMPLATE : IHtmlElementAllowingContent<TEMPLATE>, IHasAttr_shadowrootmode, IHasAttr_shadowrootdelegatesfocus, IHasAttr_shadowrootclonable, IHasAttr_shadowrootserializable, IHasAttr_shadowrootcustomelementregistry
20342034
{
20352035
public string TagName => "template";
20362036
string IHtmlElement.TagStart => "<template";

src/ProgressOnderwijsUtils/Html/TagDescription.cs

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -29,24 +29,9 @@ public bool IsPredefined
2929
);
3030

3131
static Dictionary<string, string> AttributeLookup(Type tagType, IHtmlElement emptyValue)
32-
=> typeof(AttributeConstructionMethods)
33-
.GetMethods(BindingFlags.Public | BindingFlags.Static)
34-
.Where(
35-
mi => {
36-
var typeArgument = mi.GetGenericArguments().Single();
37-
return typeArgument.GetGenericParameterConstraints()
38-
.All(
39-
constraint =>
40-
constraint.IsAssignableFrom(tagType)
41-
|| constraint == typeof(IHtmlElement<>).MakeGenericType(typeArgument) && typeof(IHtmlElement<>).MakeGenericType(tagType).IsAssignableFrom(tagType)
42-
);
43-
}
44-
)
45-
.ToDictionary(
46-
method => ((IHtmlElement)method.MakeGenericMethod(tagType).Invoke(null, new[] { emptyValue, (object)"", }).AssertNotNull()).Attributes[^1].Name,
47-
method => method.Name,
48-
StringComparer.OrdinalIgnoreCase
49-
);
32+
=> AttributeLookupTable.ByTagName.TryGetValue(emptyValue.TagName, out var lookup)
33+
? new Dictionary<string, string>(lookup, StringComparer.OrdinalIgnoreCase)
34+
: new Dictionary<string, string>(AttributeLookupTable.DefaultAttributes, StringComparer.OrdinalIgnoreCase);
5035

5136
public static TagDescription LookupTag(string tagName)
5237
=> ByTagName.TryGetValue(tagName, out var desc)
@@ -58,5 +43,5 @@ public static TagDescription LookupTag(string tagName)
5843
AttributeMethodsByName = DefaultAttributes,
5944
};
6045

61-
static readonly IReadOnlyDictionary<string, string> DefaultAttributes = AttributeLookup(typeof(CustomHtmlElement), new CustomHtmlElement("unknown", null, null));
46+
static readonly IReadOnlyDictionary<string, string> DefaultAttributes = AttributeLookupTable.DefaultAttributes;
6247
}

src/ProgressOnderwijsUtils/ProgressOnderwijsUtils.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<Import Project="..\NugetPackagesCommon.props" />
33
<PropertyGroup Label="Configuration">
4-
<Version>105.5.2</Version>
5-
<PackageReleaseNotes>Updated Nuget-packges</PackageReleaseNotes>
4+
<Version>105.6.0</Version>
5+
<PackageReleaseNotes>HTML DSL: better support for https://html.spec.whatwg.org/#boolean-attribute </PackageReleaseNotes>
66
<Title>ProgressOnderwijsUtils</Title>
77
<Description>Collection of utilities developed by ProgressOnderwijs</Description>
88
<PackageTags>ProgressOnderwijs</PackageTags>

test/ProgressOnderwijsUtils.Tests/HtmlDslGenerator.cs

Lines changed: 98 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ static readonly Uri
1818
HtmlTagKinds_GeneratedOutputFilePath = LibHtmlDirectory.Combine("HtmlSpec.HtmlTagKinds.Generated.cs"),
1919
HtmlTags_GeneratedOutputFilePath = LibHtmlDirectory.Combine("HtmlSpec.HtmlTags.Generated.cs"),
2020
AttributeNameInterfaces_GeneratedOutputFilePath = LibHtmlDirectory.Combine("HtmlSpec.AttributeNameInterfaces.Generated.cs"),
21-
AttributeConstructionMethods_GeneratedOutputFilePath = LibHtmlDirectory.Combine("HtmlSpec.AttributeConstructionMethods.Generated.cs");
21+
AttributeConstructionMethods_GeneratedOutputFilePath = LibHtmlDirectory.Combine("HtmlSpec.AttributeConstructionMethods.Generated.cs"),
22+
AttributeLookupTable_GeneratedOutputFilePath = LibHtmlDirectory.Combine("HtmlSpec.AttributeLookupTable.Generated.cs");
2223

2324
readonly ITestOutputHelper output;
2425

@@ -59,11 +60,18 @@ public async Task RegenerateHtmlTagCSharp()
5960
tableOfAttributes.QuerySelectorAll("tbody tr")
6061
.Where(tr => tr.QuerySelector("td")?.TextContent.Trim() == "HTML elements")
6162
.Select(tr => tr.QuerySelector("th").AssertNotNull().TextContent.Trim())
62-
.ToArray();
63+
.ToHashSet(StringComparer.OrdinalIgnoreCase);
6364

64-
string toClassName(string s)
65+
var booleanAttributes =
66+
tableOfAttributes.QuerySelectorAll("tbody tr").GroupBy(
67+
tr => tr.QuerySelector("th").AssertNotNull().TextContent.Trim(),
68+
tr => tr.QuerySelector("td > a[href='#boolean-attribute']")?.TextContent.Trim() == "Boolean attribute",
69+
(key, isBoolean) => (key, isBoolean: isBoolean.Distinct().ToArray() is [var unique,] ? unique : default(bool?))
70+
).ToDictionary(o => o.key, o => o.isBoolean, StringComparer.OrdinalIgnoreCase);
71+
72+
static string toClassName(string s)
6573
=> s.Replace('-', '_');
66-
string[] splitList(string list)
74+
static string[] splitList(string list)
6775
=> list.Split(';').Select(s => s.Trim().TrimEnd('*')).Where(s => s != "").ToArray();
6876

6977
var elements = tableOfElements.QuerySelectorAll("tbody tr")
@@ -119,16 +127,6 @@ string[] splitList(string list)
119127
}
120128
).ToArray();
121129

122-
var globalAttributeExtensionMethods = globalAttributes
123-
.Select(
124-
attrName => $"""
125-
{(attrName == "class" ? "\n [Obsolete]" : "")}
126-
public static THtmlTag _{toClassName(attrName)}<THtmlTag>(this THtmlTag htmlTagExpr, string? attrValue)
127-
where THtmlTag : struct, IHtmlElement<THtmlTag>
128-
=> htmlTagExpr.Attribute("{attrName}", attrValue);
129-
"""
130-
);
131-
132130
var specificAttributes = elements.SelectMany(el => el.attributes).ToDistinctArray();
133131

134132
var elAttrInterfaces = specificAttributes
@@ -138,15 +136,8 @@ public interface IHasAttr_{{toClassName(attrName)}} { }
138136
139137
"""
140138
);
141-
var elAttrExtensionMethods = specificAttributes
142-
.Select(
143-
attrName => $"""
144-
145-
public static THtmlTag _{toClassName(attrName)}<THtmlTag>(this THtmlTag htmlTagExpr, string? attrValue)
146-
where THtmlTag : struct, IHasAttr_{toClassName(attrName)}, IHtmlElement<THtmlTag>
147-
=> htmlTagExpr.Attribute("{attrName}", attrValue);
148-
"""
149-
);
139+
var elAttrExtensionMethods = globalAttributes.Concat(specificAttributes)
140+
.Select(AttrHelper).JoinStrings("");
150141

151142
var elTagNameClasses = elements
152143
.Select(
@@ -256,15 +247,97 @@ namespace ProgressOnderwijsUtils.Html.AttributeNameInterfaces;
256247
namespace ProgressOnderwijsUtils.Html;
257248
258249
public static class AttributeConstructionMethods
259-
{{{globalAttributeExtensionMethods.JoinStrings("")}}{{elAttrExtensionMethods.JoinStrings("")}}
250+
{{{elAttrExtensionMethods}}
260251
}
261252
262253
"""
263254
),
255+
256+
AssertFileExistsAndApproveContent(
257+
AttributeLookupTable_GeneratedOutputFilePath,
258+
GenerateAttributeLookupTable(
259+
elements.Select(el => (el.elementName, el.attributes)).ToArray(),
260+
globalAttributes,
261+
specificAttributes.ToArray(),
262+
booleanAttributes
263+
)
264+
),
264265
}.WhereNotNull().ToArray();
265266

266267
PAssert.That(() => diff.None());
267-
}
268+
return;
269+
270+
string GenerateAttributeLookupTable(
271+
(string elementName, DistinctArray<string> attributes)[] elements,
272+
HashSet<string> globalAttributes,
273+
string[] specificAttributes,
274+
Dictionary<string, bool?> booleanAttributes)
275+
{
276+
// Generate lookup entries for each element
277+
var elementLookups = elements.Select(el => {
278+
var elementAttributes = ((IEnumerable<string>)globalAttributes).Concat(el.attributes).ToDistinctArray();
279+
var attributeEntries = elementAttributes.Select(attrName => {
280+
var methodName = $"_{toClassName(attrName)}";
281+
return $"[\"{attrName}\"] = \"{methodName}\"";
282+
}).JoinStrings(",\n ");
283+
284+
return $"[\"" + el.elementName + "\"] = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) {\n" +
285+
$" {attributeEntries}\n" +
286+
" },";
287+
}).JoinStrings("\n ");
288+
289+
// Generate default attributes for unknown elements (global attributes only)
290+
var defaultAttributeEntries = globalAttributes.Select(attrName => {
291+
var methodName = $"_{toClassName(attrName)}";
292+
return $"[\"{attrName}\"] = \"{methodName}\"";
293+
}).JoinStrings(",\n ");
294+
295+
return $$"""
296+
#nullable enable
297+
namespace ProgressOnderwijsUtils.Html;
298+
299+
public static class AttributeLookupTable
300+
{
301+
public static readonly IReadOnlyDictionary<string, IReadOnlyDictionary<string, string>> ByTagName =
302+
new Dictionary<string, IReadOnlyDictionary<string, string>>(StringComparer.OrdinalIgnoreCase) {
303+
{{elementLookups}}
304+
};
305+
306+
public static readonly IReadOnlyDictionary<string, string> DefaultAttributes =
307+
new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) {
308+
{{defaultAttributeEntries}}
309+
};
310+
}
311+
312+
""";
313+
}
314+
315+
string AttrHelper(string attrName)
316+
{
317+
var obsoleteAttribute = attrName == "class" ? "\n [Obsolete]" : "";
318+
var applicabilityTypeContraint = globalAttributes.Contains(attrName) switch {
319+
true => "",
320+
false => $", IHasAttr_{toClassName(attrName)}",
321+
};
322+
var isBoolean = booleanAttributes.GetValueOrDefault(attrName) ?? (attrName.StartsWith("on", StringComparison.OrdinalIgnoreCase) ? false : null);
323+
return isBoolean switch {
324+
null => throw new(attrName + " could not be determined to be a boolean attribute or not"),
325+
false => AttrExtensionMethod(attrName, applicabilityTypeContraint, obsoleteAttribute, ", string? attrValue", ", attrValue"),
326+
_ => AttrExtensionMethod(attrName, applicabilityTypeContraint, obsoleteAttribute, ", bool attrValue", ", attrValue ? \"\" : null")
327+
+ AttrExtensionMethod(attrName, applicabilityTypeContraint, obsoleteAttribute, "", ", \"\"")
328+
+ AttrExtensionMethod(attrName, applicabilityTypeContraint, obsoleteAttribute, ", string? attrValue", ", attrValue"),
329+
};
330+
}
331+
332+
static string AttrExtensionMethod(string attrName, string applicabilityTypeContraint, string obsoleteAttribute, string attrValueParam, string attrValueExpr)
333+
=> $$"""
334+
{{obsoleteAttribute}}
335+
public static THtmlTag _{{toClassName(attrName)}}<THtmlTag>(this THtmlTag htmlTagExpr{{attrValueParam}})
336+
where THtmlTag : struct{{applicabilityTypeContraint}}, IHtmlElement<THtmlTag>
337+
=> htmlTagExpr.Attribute("{{attrName}}"{{attrValueExpr}});
338+
""";
339+
}
340+
268341

269342
static string? AssertFileExistsAndApproveContent(Uri GeneratedOutputFilePath, string generatedCSharpContent)
270343
{

test/ProgressOnderwijsUtils.Tests/WikiPageHtml5.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ public static HtmlFragment MakeHtml()
211211
_link._rel("stylesheet")
212212
._href("/w/load.php?debug=false&lang=en&modules=ext.cite.styles%7Cext.uls.interlanguage%7Cext.visualEditor.desktopArticleTarget.noscript%7Cext.wikimediaBadges%7Cmediawiki.legacy.commonPrint%2Cshared%7Cmediawiki.sectionAnchor%7Cmediawiki.skinning.interface%7Cskins.vector.styles%7Cwikibase.client.init&only=styles&skin=vector"),
213213
"\n",
214-
_script._async("")
214+
_script._async()
215215
._src("/w/load.php?debug=false&lang=en&modules=startup&only=scripts&skin=vector"),
216216
"\n",
217217
_meta._name("ResourceLoaderDynamicStyles")

0 commit comments

Comments
 (0)