Skip to content

Commit 7559bd7

Browse files
committed
Improve head tag parsing on template fragments
Fix #2018
1 parent 7b918d9 commit 7559bd7

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
@@ -127,10 +127,24 @@ return (function () {
127127
return "[hx-" + verb + "], [data-hx-" + verb + "]"
128128
}).join(", ");
129129

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

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

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

@@ -1099,9 +1116,8 @@ return (function () {
10991116

11001117
function findTitle(content) {
11011118
if (content.indexOf('<title') > -1) {
1102-
var contentWithSvgsRemoved = content.replace(/<svg(\s[^>]*>|>)([\s\S]*?)<\/svg>/gim, '');
1103-
var result = contentWithSvgsRemoved.match(/<title(\s[^>]*>|>)([\s\S]*?)<\/title>/im);
1104-
1119+
var contentWithSvgsRemoved = content.replace(SVG_TAGS_REGEX, '');
1120+
var result = contentWithSvgsRemoved.match(TITLE_TAG_REGEX);
11051121
if (result) {
11061122
return result[2];
11071123
}
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)