Skip to content

Commit e9bce8d

Browse files
authored
Improve head tag parsing on template fragments (#2024)
Fix #2018
1 parent 31d01c9 commit e9bce8d

File tree

5 files changed

+111
-29
lines changed

5 files changed

+111
-29
lines changed

src/htmx.js

+45-29
Original file line numberDiff line numberDiff line change
@@ -128,10 +128,24 @@ return (function () {
128128
return "[hx-" + verb + "], [data-hx-" + verb + "]"
129129
}).join(", ");
130130

131+
var HEAD_TAG_REGEX = newTagRegex('head'),
132+
TITLE_TAG_REGEX = newTagRegex('title'),
133+
SVG_TAGS_REGEX = newTagRegex('svg', true);
134+
131135
//====================================================================
132136
// Utilities
133137
//====================================================================
134138

139+
/**
140+
* @param {string} tag
141+
* @param {boolean} global
142+
* @returns {RegExp}
143+
*/
144+
function newTagRegex(tag, global = false) {
145+
return new RegExp(`<${tag}(\\s[^>]*>|>)([\\s\\S]*?)<\\/${tag}>`,
146+
global ? 'gim' : 'im');
147+
}
148+
135149
function parseInterval(str) {
136150
if (str == undefined) {
137151
return undefined
@@ -282,38 +296,41 @@ return (function () {
282296

283297
/**
284298
*
285-
* @param {string} resp
299+
* @param {string} response
286300
* @returns {Element}
287301
*/
288-
function makeFragment(resp) {
289-
var partialResponse = !aFullPageResponse(resp);
302+
function makeFragment(response) {
303+
var partialResponse = !aFullPageResponse(response);
304+
var startTag = getStartTag(response);
305+
var content = response;
306+
if (startTag === 'head') {
307+
content = content.replace(HEAD_TAG_REGEX, '');
308+
}
290309
if (htmx.config.useTemplateFragments && partialResponse) {
291-
var documentFragment = parseHTML("<body><template>" + resp + "</template></body>", 0);
310+
var documentFragment = parseHTML("<body><template>" + content + "</template></body>", 0);
292311
// @ts-ignore type mismatch between DocumentFragment and Element.
293312
// TODO: Are these close enough for htmx to use interchangeably?
294313
return documentFragment.querySelector('template').content;
295-
} else {
296-
var startTag = getStartTag(resp);
297-
switch (startTag) {
298-
case "thead":
299-
case "tbody":
300-
case "tfoot":
301-
case "colgroup":
302-
case "caption":
303-
return parseHTML("<table>" + resp + "</table>", 1);
304-
case "col":
305-
return parseHTML("<table><colgroup>" + resp + "</colgroup></table>", 2);
306-
case "tr":
307-
return parseHTML("<table><tbody>" + resp + "</tbody></table>", 2);
308-
case "td":
309-
case "th":
310-
return parseHTML("<table><tbody><tr>" + resp + "</tr></tbody></table>", 3);
311-
case "script":
312-
case "style":
313-
return parseHTML("<div>" + resp + "</div>", 1);
314-
default:
315-
return parseHTML(resp, 0);
316-
}
314+
}
315+
switch (startTag) {
316+
case "thead":
317+
case "tbody":
318+
case "tfoot":
319+
case "colgroup":
320+
case "caption":
321+
return parseHTML("<table>" + content + "</table>", 1);
322+
case "col":
323+
return parseHTML("<table><colgroup>" + content + "</colgroup></table>", 2);
324+
case "tr":
325+
return parseHTML("<table><tbody>" + content + "</tbody></table>", 2);
326+
case "td":
327+
case "th":
328+
return parseHTML("<table><tbody><tr>" + content + "</tr></tbody></table>", 3);
329+
case "script":
330+
case "style":
331+
return parseHTML("<div>" + content + "</div>", 1);
332+
default:
333+
return parseHTML(content, 0);
317334
}
318335
}
319336

@@ -1097,9 +1114,8 @@ return (function () {
10971114

10981115
function findTitle(content) {
10991116
if (content.indexOf('<title') > -1) {
1100-
var contentWithSvgsRemoved = content.replace(/<svg(\s[^>]*>|>)([\s\S]*?)<\/svg>/gim, '');
1101-
var result = contentWithSvgsRemoved.match(/<title(\s[^>]*>|>)([\s\S]*?)<\/title>/im);
1102-
1117+
var contentWithSvgsRemoved = content.replace(SVG_TAGS_REGEX, '');
1118+
var result = contentWithSvgsRemoved.match(TITLE_TAG_REGEX);
11031119
if (result) {
11041120
return result[2];
11051121
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<head>
2+
<title>Index content</title>
3+
</head>
4+
<h1># Index</h1>
5+
<ul>
6+
<li><code>&lt;title&gt;</code> <b>should not</b> spawn inside <code>&lt;main&gt;</code></li>
7+
<li><code>&lt;title&gt;</code> <b>should be</b> <em>index content</em></li>
8+
</ul>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" hx-preserve="true">
5+
<meta name="htmx-config" content='{"useTemplateFragments": true}' hx-preserve="true">
6+
7+
<title>Index content</title>
8+
9+
<style hx-preserve="true">
10+
* {
11+
font-family: Arial, sans-serif;
12+
font-size: 24px;
13+
}
14+
code {
15+
font-family: monospace;
16+
font-size: 20px;
17+
}
18+
hr { border: 1px solid black; }
19+
</style>
20+
21+
<script src="../../../src/htmx.js" hx-preserve="true"></script>
22+
<script src="../../../src/ext/head-support.js" hx-preserve="true"></script>
23+
</head>
24+
<body hx-ext="head-support" hx-boost="true">
25+
<header hx-push-url="false" hx-target="main" hx-swap="innerHTML">
26+
<nav>
27+
<ul>
28+
<li><a href="./index-partial.html">Index</a></li>
29+
<li><a href="./other-content.html">See other content</a></li>
30+
</ul>
31+
</nav><hr>
32+
</header>
33+
<main>
34+
<h1># Index</h1>
35+
<ul>
36+
<li><code>&lt;title&gt;</code> <b>should not</b> spawn inside <code>&lt;main&gt;</code></li>
37+
<li><code>&lt;title&gt;</code> <b>should be</b> <em>index content</em></li>
38+
</ul>
39+
</main>
40+
</body>
41+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<head>
2+
<title>Other content</title>
3+
<style>
4+
body {
5+
background: lightgreen;
6+
}
7+
</style>
8+
</head>
9+
<h1># Swapped content</h1>
10+
<ul>
11+
<li><code>&lt;title&gt;</code> and &lt;style&gt;
12+
<b>should not</b> spawn inside <code>&lt;main&gt;</code></li>
13+
<li><code>&lt;title&gt;</code> <b>should be</b> <em>other content</em></li>
14+
<li>Background <b>should be</b> green</li>
15+
</ul>
16+

test/manual/index.html

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ <h2>Functionality</h2>
4141
<ul>
4242
<li><a href="hxboost_relative_resources">Relative Resources</a></li>
4343
<li><a href="hxboost_template_parsing">Template Parsing</a></li>
44+
<li><a href="hxboost_partial_template_parsing">Partial Template Parsing</a></li>
4445
</ul>
4546
</li>
4647
</ul>

0 commit comments

Comments
 (0)