Skip to content
Open
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
125 changes: 117 additions & 8 deletions src/xinspect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,109 @@ namespace xcpp
}
return result;
}

std::string escape_html_attribute(const std::string& value)
{
std::string result;
result.reserve(value.size());
for (char c : value)
{
switch (c)
{
case '&':
result += "&";
break;
case '"':
result += """;
break;
case '\'':
result += "'";
break;
case '<':
result += "&lt;";
break;
case '>':
result += "&gt;";
break;
default:
result += c;
}
}
return result;
}

std::string build_cppreference_srcdoc(const std::string& inspect_result)
{
return R"(<!doctype html>
<html>
<head>
<meta charset="utf-8">
<base href=")" + escape_html_attribute(inspect_result)
+ R"(" target="_blank">
<style>
body { margin: 0; font-family: system-ui, sans-serif; }
.xcpp-status { padding: 1rem; }
main { overflow: auto; }
img { max-width: 100%; }
pre { overflow: auto; }
table { border-collapse: collapse; }
th, td { border: 1px solid #aaa; padding: .25rem .5rem; }
</style>
</head>
<body data-documentation-url=")"
+ escape_html_attribute(inspect_result) + R"(">
<p class="xcpp-status">Loading documentation… <a href=")"
+ escape_html_attribute(inspect_result) + R"(">Open cppreference</a></p>
<main></main>
<script>
const documentationUrl = document.body.dataset.documentationUrl;
const status = document.querySelector('.xcpp-status');
const content = document.querySelector('main');
const pageUrl = new URL(documentationUrl);
const page = pageUrl.pathname.replace(/^\/w\//, '').replace(/^\//, '');
const apiUrl = new URL('/mwiki/api.php', pageUrl);
apiUrl.search = new URLSearchParams({
action: 'parse',
page,
prop: 'text',
format: 'json',
formatversion: '2',
origin: '*'
});

fetch(apiUrl)
.then((response) => {
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response.json();
})
.then((data) => {
if (!data.parse || !data.parse.text) {
throw new Error('Documentation was not returned');
}
content.innerHTML = data.parse.text;
content.querySelectorAll('[href], [src]').forEach((element) => {
['href', 'src'].forEach((attribute) => {
const value = element.getAttribute(attribute);
if (value && !value.startsWith('#')) {
element.setAttribute(attribute, new URL(value, documentationUrl).href);
}
});
});
content.querySelectorAll('a').forEach((link) => {
link.target = '_blank';
link.rel = 'noopener noreferrer';
});
status.remove();
})
.catch(() => {
status.firstChild.textContent = 'Unable to load documentation inline. ';
});
</script>
</body>
</html>)";
}
}

std::string inspect(const std::string& code)
Expand Down Expand Up @@ -198,23 +301,29 @@ namespace xcpp
nl::json build_inspect_data(const std::string& inspect_result)
{
// Format html content.
std::string html_content = R"(<style>
#pager-container {
padding: 0;
margin: 0;
width: 100%;
height: 100%;
std::string iframe_content;
if (inspect_result.rfind("https://en.cppreference.com/w/", 0) == 0)
{
iframe_content = R"(sandbox="allow-scripts allow-popups allow-popups-to-escape-sandbox" srcdoc=")"
+ escape_html_attribute(build_cppreference_srcdoc(inspect_result)) + R"(")";
}
else
{
iframe_content = R"(src=")" + escape_html_attribute(inspect_result) + R"(")";
}

std::string html_content = R"(<style>
.xcpp-iframe-pager {
padding: 0;
margin: 0;
width: 100%;
height: 100%;
min-height: 20rem;
border: none;
}
</style>
<iframe class="xcpp-iframe-pager" src=")"
+ inspect_result + R"(?action=purge"></iframe>)";
<iframe class="xcpp-iframe-pager" )"
+ iframe_content + R"(></iframe>)";

auto data = nl::json::object({{"text/plain", inspect_result}, {"text/html", html_content}});
return data;
Expand Down
27 changes: 26 additions & 1 deletion test/test_interpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1014,6 +1014,31 @@ TEST_SUITE("xinspect"){
cmp.child_value = "nonexistentMethod";
REQUIRE(cmp(node) == false);
}

TEST_CASE("build_inspect_data_embeds_cppreference_content"){
std::string url = "https://en.cppreference.com/w/cpp/container/vector";
nl::json data = xcpp::build_inspect_data(url);

REQUIRE(data.contains("text/html"));
std::string html = data["text/html"].get<std::string>();
REQUIRE(html.find("<iframe") != std::string::npos);
REQUIRE(html.find("srcdoc=") != std::string::npos);
REQUIRE(html.find("/mwiki/api.php") != std::string::npos);
REQUIRE(html.find("origin") != std::string::npos);
REQUIRE(html.find(url) != std::string::npos);
REQUIRE(html.find("src=\"") == std::string::npos);
}

TEST_CASE("build_inspect_data_embeds_other_documentation_directly"){
std::string url = "https://docs.example.com/library/type";
nl::json data = xcpp::build_inspect_data(url);

REQUIRE(data.contains("text/html"));
std::string html = data["text/html"].get<std::string>();
REQUIRE(html.find("<iframe") != std::string::npos);
REQUIRE(html.find("src=\"" + url + "\"") != std::string::npos);
REQUIRE(html.find("srcdoc=") == std::string::npos);
}
}

#if !defined(__EMSCRIPTEN__)
Expand Down Expand Up @@ -1266,4 +1291,4 @@ TEST_CASE("Silent mode restores std::cout and std::cerr buffers")
REQUIRE(std::cout.rdbuf() == cout_before);
REQUIRE(std::cerr.rdbuf() == cerr_before);
}
#endif
#endif
Loading