Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 22 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,11 @@ Invalid HTML = compile error!
- [x] element name
- [x] html element validation
- [x] head element validation
- [ ] meta element validation
- [x] meta element validation
- [x] title element validation
- [x] base element validation
- [ ] link element validation
- [ ] style element validation
- [x] link element validation
- [x] style element validation
- [x] body element validation
- [x] article element validation
- [x] section element validation
Expand All @@ -158,25 +158,25 @@ Invalid HTML = compile error!
- [x] h4 element validation
- [x] h5 element validation
- [x] h6 element validation
- [ ] hgroup element validation
- [ ] header element validation
- [ ] footer element validation
- [ ] address element validation
- [ ] p element validation
- [ ] hr element validation
- [ ] pre element validation
- [ ] blockquote element validation
- [ ] ol element validation
- [ ] ul element validation
- [ ] menu element validation
- [ ] li element validation
- [ ] dl element validation
- [ ] dt element validation
- [ ] dd element validation
- [ ] figure element validation
- [ ] figcaption element validation
- [ ] main element validation
- [ ] search element validation
- [x] hgroup element validation
- [x] header element validation
- [x] footer element validation
- [x] address element validation
- [x] p element validation
- [x] hr element validation
- [x] pre element validation
- [x] blockquote element validation
- [x] ol element validation
- [x] ul element validation
- [x] menu element validation
- [x] li element validation
- [x] dl element validation
- [x] dt element validation
- [x] dd element validation
- [x] figure element validation
- [x] figcaption element validation
- [x] main element validation
- [x] search element validation
- [ ] div element validation
- [ ] a element validation
- [ ] em element validation
Expand Down
5 changes: 2 additions & 3 deletions src/html/internal/util.zig
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,9 @@ pub fn fetch_entity(field: anytype) ?Entity {
}

pub fn fetch_entity_list(any: anytype) []const Entity {
// todo: make the value dynamic
@setEvalBranchQuota(3000);
const meta_fields = std.meta.fields(@TypeOf(any));
// this doesn't look optimal at all. need to revisit the formula provided
const max_branches = meta_fields.len * 1000;
@setEvalBranchQuota(max_branches);

return struct {
fn append(fields: []const std.builtin.Type.StructField) []const Entity {
Expand Down
64 changes: 64 additions & 0 deletions src/html/validation/attribute/content.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
const std = @import("std");
const internal = @import("internal");
const util = internal.util;
const Entity = internal.entity.Entity;
const common = @import("common.zig");
const eql = std.mem.eql;
const InvalidContentAttribute = internal.constant.errors.InvalidContentAttribute;

pub fn validate_blockquote(attributes: *const []const Entity) void {
const additional_attribute = std.StaticStringMap(void).initComptime(.{
.{"cite"},
});

for (attributes.*) |attribute| {
if (common.is_global_attribute(&attribute) or common.is_global_event_handler_attribute(&attribute)) {
continue;
}

const attribute_name = util.to_lowercase(attribute.definition.attribute.name);

if (!additional_attribute.has(attribute_name)) {
@compileError(InvalidContentAttribute("The \"" ++ attribute_name ++ "\" attribute is not supported in the blockquote element."));
}
}
}

pub fn validate_ol(attributes: *const []const Entity) void {
const additional_attribute = std.StaticStringMap(void).initComptime(.{
.{"reversed"},
.{"start"},
.{"type"},
});

for (attributes.*) |attribute| {
if (common.is_global_attribute(&attribute) or common.is_global_event_handler_attribute(&attribute)) {
continue;
}

const attribute_name = util.to_lowercase(attribute.definition.attribute.name);

if (!additional_attribute.has(attribute_name)) {
@compileError(InvalidContentAttribute("The \"" ++ attribute_name ++ "\" attribute is not supported in the ol element."));
}
}
}

pub fn validate_li(attributes: *const []const Entity) void {
// TODO: find a way to validate this if and onlly if the parent is ol element
const additional_attribute = std.StaticStringMap(void).initComptime(.{
.{"value"},
});

for (attributes.*) |attribute| {
if (common.is_global_attribute(&attribute) or common.is_global_event_handler_attribute(&attribute)) {
continue;
}

const attribute_name = util.to_lowercase(attribute.definition.attribute.name);

if (!additional_attribute.has(attribute_name)) {
@compileError(InvalidContentAttribute("The \"" ++ attribute_name ++ "\" attribute is not supported in the li element."));
}
}
}
17 changes: 17 additions & 0 deletions src/html/validation/attribute/root.zig
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const util = internal.util;
const common = @import("common.zig");
const metadata = @import("metadata.zig");
const section = @import("section.zig");
const content = @import("content.zig");
const Entity = internal.entity.Entity;

pub fn validate_attributes(element_name: []const u8, attributes: *const []const Entity) void {
Expand Down Expand Up @@ -36,4 +37,20 @@ const validations = std.StaticStringMap(*const fn (*const []const Entity) void).
.{ "header", &common.global_attribute_and_global_event_handler_only },
.{ "footer", &common.global_attribute_and_global_event_handler_only },
.{ "address", &common.global_attribute_and_global_event_handler_only },
.{ "p", &common.global_attribute_and_global_event_handler_only },
.{ "hr", &common.global_attribute_and_global_event_handler_only },
.{ "pre", &common.global_attribute_and_global_event_handler_only },
.{ "blockquote", &content.validate_blockquote },
.{ "ol", &content.validate_ol },
.{ "ul", &common.global_attribute_and_global_event_handler_only },
.{ "menu", &common.global_attribute_and_global_event_handler_only },
.{ "li", &content.validate_li },
.{ "dl", &common.global_attribute_and_global_event_handler_only },
.{ "dt", &common.global_attribute_and_global_event_handler_only },
.{ "dd", &common.global_attribute_and_global_event_handler_only },
.{ "figure", &common.global_attribute_and_global_event_handler_only },
.{ "figcaption", &common.global_attribute_and_global_event_handler_only },
.{ "main", &common.global_attribute_and_global_event_handler_only },
.{ "search", &common.global_attribute_and_global_event_handler_only },
.{ "div", &common.global_attribute_and_global_event_handler_only },
});
22 changes: 19 additions & 3 deletions src/html/validation/element/common.zig
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const internal = @import("internal");
const constant = internal.constant;
const util = internal.util;
const Entity = internal.entity.Entity;
const InvalidContentModel = internal.constant.errors.InvalidContentModel;

Expand All @@ -15,9 +16,24 @@ pub fn validate_flow_content(children: *const []const Entity) void {
continue;
}

const element_name = child.definition.element.name;
if (!constant.content.FLOW_CONTENT.has(element_name)) {
@compileError(InvalidContentModel("Only flow content is supported as the child element."));
const child_name = util.to_lowercase(child.definition.element.name);

if (!constant.content.FLOW_CONTENT.has(child_name)) {
@compileError(InvalidContentModel("Only flow content is supported as the element descendants."));
}
}
}

pub fn validate_phrasing_content(children: *const []const Entity) void {
for (children.*) |child| {
if (child.definition != .element) {
continue;
}

const child_name = util.to_lowercase(child.definition.element.name);

if (!constant.content.PHRASING_CONTENT.has(child_name)) {
@compileError(InvalidContentModel("Only phrasing content is supported as the element descendants."));
}
}
}
187 changes: 187 additions & 0 deletions src/html/validation/element/content.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
const std = @import("std");
const internal = @import("internal");
const util = internal.util;
const constant = internal.constant;
const Entity = internal.entity.Entity;
const eql = std.mem.eql;
const InvalidContentModel = internal.constant.errors.InvalidContentModel;

pub fn validate_listing(children: *const []const Entity) void {
const supported_child = std.StaticStringMap(void).initComptime(.{
.{"li"},
.{"script"},
.{"template"},
});

for (children.*) |child| {
if (child.definition == .comment) {
continue;
}

if (child.definition == .text) {
@compileError(InvalidContentModel("Text is not supported as a direct descendant of this element."));
}

const child_name = util.to_lowercase(child.definition.element.name);

if (!supported_child.has(child_name)) {
@compileError(InvalidContentModel("The \"" ++ child_name ++ "\" element is not supported in this element."));
}
}
}

const supported_dl_child = std.StaticStringMap(void).initComptime(.{
.{"dt"},
.{"dd"},
.{"div"},
.{"script"},
.{"template"},
});

pub fn validate_dl(children: *const []const Entity) void {
comptime var found_div = false;
comptime var found_dt_or_dd = false;

for (children.*) |child| {
if (child.definition == .comment) {
continue;
}

if (child.definition == .text) {
@compileError(InvalidContentModel("Text is not supported as a direct descendant of this element."));
}

const child_name = util.to_lowercase(child.definition.element.name);

if (!supported_dl_child.has(child_name)) {
@compileError(InvalidContentModel("The \"" ++ child_name ++ "\" element is not supported in the dl element."));
}

if (eql(u8, child_name, "div")) {
found_div = true;
}

if (eql(u8, child_name, "dt") or eql(u8, child_name, "dd")) {
found_dt_or_dd = true;
}
}

if ((!found_div and !found_dt_or_dd) or (found_div and found_dt_or_dd)) {
@compileError(InvalidContentModel("The dl element must have either both dt and dd as its descendants, or div with dt and dd as its descendants."));
}

if (found_div) {
for (children.*) |child| {
if (child.definition == .element) {
const child_name = util.to_lowercase(child.definition.element.name);
if (eql(u8, child_name, "div")) {
validate_dl_internal(&child.definition.element.chlidren);
}
}
}
} else {
validate_dl_internal(children);
}
}

fn validate_dl_internal(children: *const []const Entity) void {
comptime var last_elm = "__";
comptime var found_dt = false;
comptime var has_one_valid_group = false;

for (children.*) |child| {
if (child.definition == .comment) {
continue;
}

if (child.definition == .text) {
@compileError(InvalidContentModel("Text is not supported as a direct descendant of this element."));
}

const child_name = util.to_lowercase(child.definition.element.name);

if (!supported_dl_child.has(child_name)) {
@compileError(InvalidContentModel("The \"" ++ child_name ++ "\" element is not supported in the dl element."));
}

if (eql(u8, child_name, "div")) {
@compileError(InvalidContentModel("A nested div element is not supported."));
}

if (eql(u8, child_name, "dt")) {
if (eql(u8, last_elm, "dt")) {
@compileError(InvalidContentModel("The next valid descendant after the dt element is the dd element."));
}
last_elm = "dt";
found_dt = true;
}

if (eql(u8, child_name, "dd")) {
if (!found_dt and !eql(u8, last_elm, "dd")) {
@compileError(InvalidContentModel("The dd element must be after exactly one dt element."));
}
last_elm = "dd";
found_dt = false;
has_one_valid_group = true;
}
}

if (!has_one_valid_group) {
@compileError(InvalidContentModel("There must be at least one group of dt element that is followed by one or more dd elements."));
}
}

pub fn validate_dt(children: *const []const Entity) void {
for (children.*) |child| {
if (child.definition != .element) {
continue;
}

const child_name = util.to_lowercase(child.definition.element.name);
const error_message = "The \"" ++ child_name ++ "\" element is not supported in the dt element.";

if (!constant.content.FLOW_CONTENT.has(child_name)) {
@compileError(InvalidContentModel(error_message));
}

if (eql(u8, child_name, "header") or eql(u8, child_name, "footer")) {
@compileError(InvalidContentModel(error_message));
}

if (constant.content.SECTIONING_CONTENT.has(child_name) or constant.content.HEADING_CONTENT.has(child_name)) {
@compileError(InvalidContentModel(error_message));
}
}
}

pub fn validate_figure(children: *const []const Entity) void {
comptime var figcaption_count = 0;
comptime var flow_content_count = 0;

for (children.*) |child| {
if (child.definition != .element) {
continue;
}

const child_name = util.to_lowercase(child.definition.element.name);

if (constant.content.FLOW_CONTENT.has(child_name)) {
flow_content_count += 1;
continue;
}

if (!eql(u8, child_name, "figcaption")) {
@compileError(InvalidContentModel("The \"" ++ child_name ++ "\" element is not supported in the figure element."));
}

figcaption_count += 1;
}

if (figcaption_count > 1) {
@compileError(InvalidContentModel("Only one figcaption element supported in the figure element."));
}

if (figcaption_count == 1 and flow_content_count == 0) {
@compileError(InvalidContentModel("At least one flow content is required after/before the figcaption element."));
}
}
Loading