@@ -231,6 +231,85 @@ pub const Element = struct {
231231 }
232232 }
233233
234+ /// Parses the given `input` string and inserts its children to an element at given `position`.
235+ /// https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentHTML
236+ ///
237+ /// TODO: Support for XML parsing and `TrustedHTML` instances.
238+ pub fn _insertAdjacentHTML (self : * parser.Element , position : []const u8 , input : []const u8 ) ! void {
239+ const self_node = parser .elementToNode (self );
240+ const doc = parser .nodeOwnerDocument (self_node ) orelse {
241+ return parser .DOMError .WrongDocument ;
242+ };
243+
244+ // Parse the fragment.
245+ // Should return error.Syntax on fail?
246+ const fragment = try parser .documentParseFragmentFromStr (doc , input );
247+ const fragment_node = parser .documentFragmentToNode (fragment );
248+
249+ // We always get it wrapped like so:
250+ // <html><head></head><body>{ ... }</body></html>
251+ // None of the following can be null.
252+ const maybe_html = parser .nodeFirstChild (fragment_node );
253+ std .debug .assert (maybe_html != null );
254+ const html = maybe_html orelse return ;
255+
256+ const maybe_body = parser .nodeLastChild (html );
257+ std .debug .assert (maybe_body != null );
258+ const body = maybe_body orelse return ;
259+
260+ const children = try parser .nodeGetChildNodes (body );
261+
262+ // * `target_node` is `*Node` (where we actually insert),
263+ // * `prev_node` is `?*Node`.
264+ const target_node , const prev_node = blk : {
265+ // Prefer case-sensitive match.
266+ // "beforeend" was the most common case in my tests; we might adjust the order
267+ // depending on which ones websites prefer most.
268+ if (std .mem .eql (u8 , position , "beforeend" )) {
269+ break :blk .{ self_node , null };
270+ }
271+
272+ if (std .mem .eql (u8 , position , "afterbegin" )) {
273+ // Get the first child; null indicates there are no children.
274+ const first_child = parser .nodeFirstChild (self_node );
275+ break :blk .{ self_node , first_child };
276+ }
277+
278+ if (std .mem .eql (u8 , position , "beforebegin" )) {
279+ // The node must have a parent node in order to use this variant.
280+ const parent = parser .nodeParentNode (self_node ) orelse return error .NoModificationAllowed ;
281+ // Parent cannot be Document.
282+ // Should have checks for document_fragment and document_type?
283+ if (parser .nodeType (parent ) == .document ) {
284+ return error .NoModificationAllowed ;
285+ }
286+
287+ break :blk .{ parent , self_node };
288+ }
289+
290+ if (std .mem .eql (u8 , position , "afterend" )) {
291+ // The node must have a parent node in order to use this variant.
292+ const parent = parser .nodeParentNode (self_node ) orelse return error .NoModificationAllowed ;
293+ // Parent cannot be Document.
294+ if (parser .nodeType (parent ) == .document ) {
295+ return error .NoModificationAllowed ;
296+ }
297+ // Get the next sibling or null; null indicates our node is the only one.
298+ const sibling = parser .nodeNextSibling (self_node );
299+ break :blk .{ parent , sibling };
300+ }
301+
302+ // Thrown if:
303+ // * position is not one of the four listed values.
304+ // * The input is XML that is not well-formed.
305+ return error .Syntax ;
306+ };
307+
308+ while (parser .nodeListItem (children , 0 )) | child | {
309+ _ = try parser .nodeInsertBefore (target_node , child , prev_node );
310+ }
311+ }
312+
234313 // The closest() method of the Element interface traverses the element and its parents (heading toward the document root) until it finds a node that matches the specified CSS selector.
235314 // Returns the closest ancestor Element or itself, which matches the selectors. If there are no such element, null.
236315 pub fn _closest (self : * parser.Element , selector : []const u8 , page : * Page ) ! ? * parser.Element {
0 commit comments