Skip to content

Commit 84d07f3

Browse files
Merge pull request #919 from lightpanda-io/html_element-and-element
Create HTMLElement instead of pure Element
2 parents d61e91b + 0fee2bb commit 84d07f3

File tree

10 files changed

+175
-115
lines changed

10 files changed

+175
-115
lines changed

src/browser/dom/character_data.zig

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ const Node = @import("node.zig").Node;
2424
const Comment = @import("comment.zig").Comment;
2525
const Text = @import("text.zig");
2626
const ProcessingInstruction = @import("processing_instruction.zig").ProcessingInstruction;
27-
const HTMLElem = @import("../html/elements.zig");
27+
const Element = @import("element.zig").Element;
28+
const ElementUnion = @import("element.zig").Union;
2829

2930
// CharacterData interfaces
3031
pub const Interfaces = .{
@@ -49,20 +50,20 @@ pub const CharacterData = struct {
4950
return try parser.characterDataLength(self);
5051
}
5152

52-
pub fn get_nextElementSibling(self: *parser.CharacterData) !?HTMLElem.Union {
53+
pub fn get_nextElementSibling(self: *parser.CharacterData) !?ElementUnion {
5354
const res = try parser.nodeNextElementSibling(parser.characterDataToNode(self));
5455
if (res == null) {
5556
return null;
5657
}
57-
return try HTMLElem.toInterface(HTMLElem.Union, res.?);
58+
return try Element.toInterface(res.?);
5859
}
5960

60-
pub fn get_previousElementSibling(self: *parser.CharacterData) !?HTMLElem.Union {
61+
pub fn get_previousElementSibling(self: *parser.CharacterData) !?ElementUnion {
6162
const res = try parser.nodePreviousElementSibling(parser.characterDataToNode(self));
6263
if (res == null) {
6364
return null;
6465
}
65-
return try HTMLElem.toInterface(HTMLElem.Union, res.?);
66+
return try Element.toInterface(res.?);
6667
}
6768

6869
// Read/Write attributes

src/browser/dom/document.zig

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,8 @@ pub const Document = struct {
6666
return DOMImplementation{};
6767
}
6868

69-
pub fn get_documentElement(self: *parser.Document) !?ElementUnion {
70-
const e = try parser.documentGetDocumentElement(self);
71-
if (e == null) return null;
72-
return try Element.toInterface(e.?);
69+
pub fn get_documentElement(self: *parser.Document) !?*parser.Element {
70+
return try parser.documentGetDocumentElement(self);
7371
}
7472

7573
pub fn get_documentURI(self: *parser.Document) ![]const u8 {

src/browser/dom/element.zig

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,23 @@ pub const Element = struct {
5555
};
5656

5757
pub fn toInterface(e: *parser.Element) !Union {
58-
return try HTMLElem.toInterface(Union, e);
59-
// SVGElement and MathML are not supported yet.
58+
return toInterfaceT(Union, e);
59+
}
60+
61+
pub fn toInterfaceT(comptime T: type, e: *parser.Element) !T {
62+
const tagname = try parser.elementGetTagName(e) orelse {
63+
// in case of null tagname, return the element as it.
64+
return .{ .Element = e };
65+
};
66+
67+
// TODO SVGElement and MathML are not supported yet.
68+
69+
const tag = parser.Tag.fromString(tagname) catch {
70+
// if the tag is invalid, we don't have an HTMLElement.
71+
return .{ .Element = e };
72+
};
73+
74+
return HTMLElem.toInterfaceFromTag(T, e, tag);
6075
}
6176

6277
// JS funcs
@@ -344,13 +359,13 @@ pub const Element = struct {
344359
pub fn get_previousElementSibling(self: *parser.Element) !?Union {
345360
const res = try parser.nodePreviousElementSibling(parser.elementToNode(self));
346361
if (res == null) return null;
347-
return try HTMLElem.toInterface(HTMLElem.Union, res.?);
362+
return try toInterface(res.?);
348363
}
349364

350365
pub fn get_nextElementSibling(self: *parser.Element) !?Union {
351366
const res = try parser.nodeNextElementSibling(parser.elementToNode(self));
352367
if (res == null) return null;
353-
return try HTMLElem.toInterface(HTMLElem.Union, res.?);
368+
return try toInterface(res.?);
354369
}
355370

356371
fn getElementById(self: *parser.Element, id: []const u8) !?*parser.Node {

src/browser/dom/node.zig

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const EventTarget = @import("event_target.zig").EventTarget;
2929
const Attr = @import("attribute.zig").Attr;
3030
const CData = @import("character_data.zig");
3131
const Element = @import("element.zig").Element;
32+
const ElementUnion = @import("element.zig").Union;
3233
const NodeList = @import("nodelist.zig").NodeList;
3334
const Document = @import("document.zig").Document;
3435
const DocumentType = @import("document_type.zig").DocumentType;
@@ -40,7 +41,6 @@ const Walker = @import("walker.zig").WalkerDepthFirst;
4041

4142
// HTML
4243
const HTML = @import("../html/html.zig");
43-
const HTMLElem = @import("../html/elements.zig");
4444

4545
// Node interfaces
4646
pub const Interfaces = .{
@@ -67,7 +67,7 @@ pub const Node = struct {
6767

6868
pub fn toInterface(node: *parser.Node) !Union {
6969
return switch (try parser.nodeType(node)) {
70-
.element => try HTMLElem.toInterface(
70+
.element => try Element.toInterfaceT(
7171
Union,
7272
@as(*parser.Element, @ptrCast(node)),
7373
),
@@ -145,12 +145,12 @@ pub const Node = struct {
145145
return try Node.toInterface(res.?);
146146
}
147147

148-
pub fn get_parentElement(self: *parser.Node) !?HTMLElem.Union {
148+
pub fn get_parentElement(self: *parser.Node) !?ElementUnion {
149149
const res = try parser.nodeParentElement(self);
150150
if (res == null) {
151151
return null;
152152
}
153-
return try HTMLElem.toInterface(HTMLElem.Union, @as(*parser.Element, @ptrCast(res.?)));
153+
return try Element.toInterface(res.?);
154154
}
155155

156156
pub fn get_nodeName(self: *parser.Node) ![]const u8 {

src/browser/dump.zig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ pub fn writeNode(node: *parser.Node, opts: Opts, writer: anytype) anyerror!void
6262
switch (try parser.nodeType(node)) {
6363
.element => {
6464
// open the tag
65-
const tag_type = try parser.elementHTMLGetTagType(@ptrCast(node));
65+
const tag_type = try parser.nodeHTMLGetTagType(node) orelse .undef;
6666
if (tag_type == .script and opts.exclude_scripts) {
6767
return;
6868
}
@@ -150,7 +150,7 @@ pub fn writeChildren(root: *parser.Node, opts: Opts, writer: anytype) !void {
150150
// area, base, br, col, embed, hr, img, input, link, meta, source, track, wbr
151151
// https://html.spec.whatwg.org/#void-elements
152152
fn isVoid(elem: *parser.Element) !bool {
153-
const tag = try parser.elementHTMLGetTagType(@as(*parser.ElementHTML, @ptrCast(elem)));
153+
const tag = try parser.elementTag(elem);
154154
return switch (tag) {
155155
.area, .base, .br, .col, .embed, .hr, .img, .input, .link => true,
156156
.meta, .source, .track, .wbr => true,

src/browser/html/elements.zig

Lines changed: 69 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ const CSSStyleDeclaration = @import("../cssom/CSSStyleDeclaration.zig");
3434

3535
// HTMLElement interfaces
3636
pub const Interfaces = .{
37+
Element,
3738
HTMLElement,
3839
HTMLUnknownElement,
3940
HTMLAnchorElement,
@@ -1108,78 +1109,75 @@ pub const HTMLVideoElement = struct {
11081109
pub const subtype = .node;
11091110
};
11101111

1111-
pub fn toInterface(comptime T: type, e: *parser.Element) !T {
1112-
const elem: *align(@alignOf(*parser.Element)) parser.Element = @alignCast(e);
1113-
const tag = try parser.elementHTMLGetTagType(@as(*parser.ElementHTML, @ptrCast(elem)));
1114-
1112+
pub fn toInterfaceFromTag(comptime T: type, e: *parser.Element, tag: parser.Tag) !T {
11151113
return switch (tag) {
1116-
.abbr, .acronym, .address, .article, .aside, .b, .basefont, .bdi, .bdo, .bgsound, .big, .center, .cite, .code, .dd, .details, .dfn, .dt, .em, .figcaption, .figure, .footer, .header, .hgroup, .i, .isindex, .keygen, .kbd, .main, .mark, .marquee, .menu, .menuitem, .nav, .nobr, .noframes, .noscript, .rp, .rt, .ruby, .s, .samp, .section, .small, .spacer, .strike, .strong, .sub, .summary, .sup, .tt, .u, .wbr, ._var => .{ .HTMLElement = @as(*parser.ElementHTML, @ptrCast(elem)) },
1117-
.a => .{ .HTMLAnchorElement = @as(*parser.Anchor, @ptrCast(elem)) },
1118-
.applet => .{ .HTMLAppletElement = @as(*parser.Applet, @ptrCast(elem)) },
1119-
.area => .{ .HTMLAreaElement = @as(*parser.Area, @ptrCast(elem)) },
1120-
.audio => .{ .HTMLAudioElement = @as(*parser.Audio, @ptrCast(elem)) },
1121-
.base => .{ .HTMLBaseElement = @as(*parser.Base, @ptrCast(elem)) },
1122-
.body => .{ .HTMLBodyElement = @as(*parser.Body, @ptrCast(elem)) },
1123-
.br => .{ .HTMLBRElement = @as(*parser.BR, @ptrCast(elem)) },
1124-
.button => .{ .HTMLButtonElement = @as(*parser.Button, @ptrCast(elem)) },
1125-
.canvas => .{ .HTMLCanvasElement = @as(*parser.Canvas, @ptrCast(elem)) },
1126-
.dl => .{ .HTMLDListElement = @as(*parser.DList, @ptrCast(elem)) },
1127-
.data => .{ .HTMLDataElement = @as(*parser.Data, @ptrCast(elem)) },
1128-
.datalist => .{ .HTMLDataListElement = @as(*parser.DataList, @ptrCast(elem)) },
1129-
.dialog => .{ .HTMLDialogElement = @as(*parser.Dialog, @ptrCast(elem)) },
1130-
.dir => .{ .HTMLDirectoryElement = @as(*parser.Directory, @ptrCast(elem)) },
1131-
.div => .{ .HTMLDivElement = @as(*parser.Div, @ptrCast(elem)) },
1132-
.embed => .{ .HTMLEmbedElement = @as(*parser.Embed, @ptrCast(elem)) },
1133-
.fieldset => .{ .HTMLFieldSetElement = @as(*parser.FieldSet, @ptrCast(elem)) },
1134-
.font => .{ .HTMLFontElement = @as(*parser.Font, @ptrCast(elem)) },
1135-
.form => .{ .HTMLFormElement = @as(*parser.Form, @ptrCast(elem)) },
1136-
.frame => .{ .HTMLFrameElement = @as(*parser.Frame, @ptrCast(elem)) },
1137-
.frameset => .{ .HTMLFrameSetElement = @as(*parser.FrameSet, @ptrCast(elem)) },
1138-
.hr => .{ .HTMLHRElement = @as(*parser.HR, @ptrCast(elem)) },
1139-
.head => .{ .HTMLHeadElement = @as(*parser.Head, @ptrCast(elem)) },
1140-
.h1, .h2, .h3, .h4, .h5, .h6 => .{ .HTMLHeadingElement = @as(*parser.Heading, @ptrCast(elem)) },
1141-
.html => .{ .HTMLHtmlElement = @as(*parser.Html, @ptrCast(elem)) },
1142-
.iframe => .{ .HTMLIFrameElement = @as(*parser.IFrame, @ptrCast(elem)) },
1143-
.img => .{ .HTMLImageElement = @as(*parser.Image, @ptrCast(elem)) },
1144-
.input => .{ .HTMLInputElement = @as(*parser.Input, @ptrCast(elem)) },
1145-
.li => .{ .HTMLLIElement = @as(*parser.LI, @ptrCast(elem)) },
1146-
.label => .{ .HTMLLabelElement = @as(*parser.Label, @ptrCast(elem)) },
1147-
.legend => .{ .HTMLLegendElement = @as(*parser.Legend, @ptrCast(elem)) },
1148-
.link => .{ .HTMLLinkElement = @as(*parser.Link, @ptrCast(elem)) },
1149-
.map => .{ .HTMLMapElement = @as(*parser.Map, @ptrCast(elem)) },
1150-
.meta => .{ .HTMLMetaElement = @as(*parser.Meta, @ptrCast(elem)) },
1151-
.meter => .{ .HTMLMeterElement = @as(*parser.Meter, @ptrCast(elem)) },
1152-
.ins, .del => .{ .HTMLModElement = @as(*parser.Mod, @ptrCast(elem)) },
1153-
.ol => .{ .HTMLOListElement = @as(*parser.OList, @ptrCast(elem)) },
1154-
.object => .{ .HTMLObjectElement = @as(*parser.Object, @ptrCast(elem)) },
1155-
.optgroup => .{ .HTMLOptGroupElement = @as(*parser.OptGroup, @ptrCast(elem)) },
1156-
.option => .{ .HTMLOptionElement = @as(*parser.Option, @ptrCast(elem)) },
1157-
.output => .{ .HTMLOutputElement = @as(*parser.Output, @ptrCast(elem)) },
1158-
.p => .{ .HTMLParagraphElement = @as(*parser.Paragraph, @ptrCast(elem)) },
1159-
.param => .{ .HTMLParamElement = @as(*parser.Param, @ptrCast(elem)) },
1160-
.picture => .{ .HTMLPictureElement = @as(*parser.Picture, @ptrCast(elem)) },
1161-
.pre => .{ .HTMLPreElement = @as(*parser.Pre, @ptrCast(elem)) },
1162-
.progress => .{ .HTMLProgressElement = @as(*parser.Progress, @ptrCast(elem)) },
1163-
.blockquote, .q => .{ .HTMLQuoteElement = @as(*parser.Quote, @ptrCast(elem)) },
1164-
.script => .{ .HTMLScriptElement = @as(*parser.Script, @ptrCast(elem)) },
1165-
.select => .{ .HTMLSelectElement = @as(*parser.Select, @ptrCast(elem)) },
1166-
.source => .{ .HTMLSourceElement = @as(*parser.Source, @ptrCast(elem)) },
1167-
.span => .{ .HTMLSpanElement = @as(*parser.Span, @ptrCast(elem)) },
1168-
.style => .{ .HTMLStyleElement = @as(*parser.Style, @ptrCast(elem)) },
1169-
.table => .{ .HTMLTableElement = @as(*parser.Table, @ptrCast(elem)) },
1170-
.caption => .{ .HTMLTableCaptionElement = @as(*parser.TableCaption, @ptrCast(elem)) },
1171-
.th, .td => .{ .HTMLTableCellElement = @as(*parser.TableCell, @ptrCast(elem)) },
1172-
.col, .colgroup => .{ .HTMLTableColElement = @as(*parser.TableCol, @ptrCast(elem)) },
1173-
.tr => .{ .HTMLTableRowElement = @as(*parser.TableRow, @ptrCast(elem)) },
1174-
.thead, .tbody, .tfoot => .{ .HTMLTableSectionElement = @as(*parser.TableSection, @ptrCast(elem)) },
1175-
.template => .{ .HTMLTemplateElement = @as(*parser.Template, @ptrCast(elem)) },
1176-
.textarea => .{ .HTMLTextAreaElement = @as(*parser.TextArea, @ptrCast(elem)) },
1177-
.time => .{ .HTMLTimeElement = @as(*parser.Time, @ptrCast(elem)) },
1178-
.title => .{ .HTMLTitleElement = @as(*parser.Title, @ptrCast(elem)) },
1179-
.track => .{ .HTMLTrackElement = @as(*parser.Track, @ptrCast(elem)) },
1180-
.ul => .{ .HTMLUListElement = @as(*parser.UList, @ptrCast(elem)) },
1181-
.video => .{ .HTMLVideoElement = @as(*parser.Video, @ptrCast(elem)) },
1182-
.undef => .{ .HTMLUnknownElement = @as(*parser.Unknown, @ptrCast(elem)) },
1114+
.abbr, .acronym, .address, .article, .aside, .b, .basefont, .bdi, .bdo, .bgsound, .big, .center, .cite, .code, .dd, .details, .dfn, .dt, .em, .figcaption, .figure, .footer, .header, .hgroup, .i, .isindex, .keygen, .kbd, .main, .mark, .marquee, .menu, .menuitem, .nav, .nobr, .noframes, .noscript, .rp, .rt, .ruby, .s, .samp, .section, .small, .spacer, .strike, .strong, .sub, .summary, .sup, .tt, .u, .wbr, ._var => .{ .HTMLElement = @as(*parser.ElementHTML, @ptrCast(e)) },
1115+
.a => .{ .HTMLAnchorElement = @as(*parser.Anchor, @ptrCast(e)) },
1116+
.applet => .{ .HTMLAppletElement = @as(*parser.Applet, @ptrCast(e)) },
1117+
.area => .{ .HTMLAreaElement = @as(*parser.Area, @ptrCast(e)) },
1118+
.audio => .{ .HTMLAudioElement = @as(*parser.Audio, @ptrCast(e)) },
1119+
.base => .{ .HTMLBaseElement = @as(*parser.Base, @ptrCast(e)) },
1120+
.body => .{ .HTMLBodyElement = @as(*parser.Body, @ptrCast(e)) },
1121+
.br => .{ .HTMLBRElement = @as(*parser.BR, @ptrCast(e)) },
1122+
.button => .{ .HTMLButtonElement = @as(*parser.Button, @ptrCast(e)) },
1123+
.canvas => .{ .HTMLCanvasElement = @as(*parser.Canvas, @ptrCast(e)) },
1124+
.dl => .{ .HTMLDListElement = @as(*parser.DList, @ptrCast(e)) },
1125+
.data => .{ .HTMLDataElement = @as(*parser.Data, @ptrCast(e)) },
1126+
.datalist => .{ .HTMLDataListElement = @as(*parser.DataList, @ptrCast(e)) },
1127+
.dialog => .{ .HTMLDialogElement = @as(*parser.Dialog, @ptrCast(e)) },
1128+
.dir => .{ .HTMLDirectoryElement = @as(*parser.Directory, @ptrCast(e)) },
1129+
.div => .{ .HTMLDivElement = @as(*parser.Div, @ptrCast(e)) },
1130+
.embed => .{ .HTMLEmbedElement = @as(*parser.Embed, @ptrCast(e)) },
1131+
.fieldset => .{ .HTMLFieldSetElement = @as(*parser.FieldSet, @ptrCast(e)) },
1132+
.font => .{ .HTMLFontElement = @as(*parser.Font, @ptrCast(e)) },
1133+
.form => .{ .HTMLFormElement = @as(*parser.Form, @ptrCast(e)) },
1134+
.frame => .{ .HTMLFrameElement = @as(*parser.Frame, @ptrCast(e)) },
1135+
.frameset => .{ .HTMLFrameSetElement = @as(*parser.FrameSet, @ptrCast(e)) },
1136+
.hr => .{ .HTMLHRElement = @as(*parser.HR, @ptrCast(e)) },
1137+
.head => .{ .HTMLHeadElement = @as(*parser.Head, @ptrCast(e)) },
1138+
.h1, .h2, .h3, .h4, .h5, .h6 => .{ .HTMLHeadingElement = @as(*parser.Heading, @ptrCast(e)) },
1139+
.html => .{ .HTMLHtmlElement = @as(*parser.Html, @ptrCast(e)) },
1140+
.iframe => .{ .HTMLIFrameElement = @as(*parser.IFrame, @ptrCast(e)) },
1141+
.img => .{ .HTMLImageElement = @as(*parser.Image, @ptrCast(e)) },
1142+
.input => .{ .HTMLInputElement = @as(*parser.Input, @ptrCast(e)) },
1143+
.li => .{ .HTMLLIElement = @as(*parser.LI, @ptrCast(e)) },
1144+
.label => .{ .HTMLLabelElement = @as(*parser.Label, @ptrCast(e)) },
1145+
.legend => .{ .HTMLLegendElement = @as(*parser.Legend, @ptrCast(e)) },
1146+
.link => .{ .HTMLLinkElement = @as(*parser.Link, @ptrCast(e)) },
1147+
.map => .{ .HTMLMapElement = @as(*parser.Map, @ptrCast(e)) },
1148+
.meta => .{ .HTMLMetaElement = @as(*parser.Meta, @ptrCast(e)) },
1149+
.meter => .{ .HTMLMeterElement = @as(*parser.Meter, @ptrCast(e)) },
1150+
.ins, .del => .{ .HTMLModElement = @as(*parser.Mod, @ptrCast(e)) },
1151+
.ol => .{ .HTMLOListElement = @as(*parser.OList, @ptrCast(e)) },
1152+
.object => .{ .HTMLObjectElement = @as(*parser.Object, @ptrCast(e)) },
1153+
.optgroup => .{ .HTMLOptGroupElement = @as(*parser.OptGroup, @ptrCast(e)) },
1154+
.option => .{ .HTMLOptionElement = @as(*parser.Option, @ptrCast(e)) },
1155+
.output => .{ .HTMLOutputElement = @as(*parser.Output, @ptrCast(e)) },
1156+
.p => .{ .HTMLParagraphElement = @as(*parser.Paragraph, @ptrCast(e)) },
1157+
.param => .{ .HTMLParamElement = @as(*parser.Param, @ptrCast(e)) },
1158+
.picture => .{ .HTMLPictureElement = @as(*parser.Picture, @ptrCast(e)) },
1159+
.pre => .{ .HTMLPreElement = @as(*parser.Pre, @ptrCast(e)) },
1160+
.progress => .{ .HTMLProgressElement = @as(*parser.Progress, @ptrCast(e)) },
1161+
.blockquote, .q => .{ .HTMLQuoteElement = @as(*parser.Quote, @ptrCast(e)) },
1162+
.script => .{ .HTMLScriptElement = @as(*parser.Script, @ptrCast(e)) },
1163+
.select => .{ .HTMLSelectElement = @as(*parser.Select, @ptrCast(e)) },
1164+
.source => .{ .HTMLSourceElement = @as(*parser.Source, @ptrCast(e)) },
1165+
.span => .{ .HTMLSpanElement = @as(*parser.Span, @ptrCast(e)) },
1166+
.style => .{ .HTMLStyleElement = @as(*parser.Style, @ptrCast(e)) },
1167+
.table => .{ .HTMLTableElement = @as(*parser.Table, @ptrCast(e)) },
1168+
.caption => .{ .HTMLTableCaptionElement = @as(*parser.TableCaption, @ptrCast(e)) },
1169+
.th, .td => .{ .HTMLTableCellElement = @as(*parser.TableCell, @ptrCast(e)) },
1170+
.col, .colgroup => .{ .HTMLTableColElement = @as(*parser.TableCol, @ptrCast(e)) },
1171+
.tr => .{ .HTMLTableRowElement = @as(*parser.TableRow, @ptrCast(e)) },
1172+
.thead, .tbody, .tfoot => .{ .HTMLTableSectionElement = @as(*parser.TableSection, @ptrCast(e)) },
1173+
.template => .{ .HTMLTemplateElement = @as(*parser.Template, @ptrCast(e)) },
1174+
.textarea => .{ .HTMLTextAreaElement = @as(*parser.TextArea, @ptrCast(e)) },
1175+
.time => .{ .HTMLTimeElement = @as(*parser.Time, @ptrCast(e)) },
1176+
.title => .{ .HTMLTitleElement = @as(*parser.Title, @ptrCast(e)) },
1177+
.track => .{ .HTMLTrackElement = @as(*parser.Track, @ptrCast(e)) },
1178+
.ul => .{ .HTMLUListElement = @as(*parser.UList, @ptrCast(e)) },
1179+
.video => .{ .HTMLVideoElement = @as(*parser.Video, @ptrCast(e)) },
1180+
.undef => .{ .HTMLUnknownElement = @as(*parser.Unknown, @ptrCast(e)) },
11831181
};
11841182
}
11851183

0 commit comments

Comments
 (0)