2018-11-03 17:12:20 +01:00
|
|
|
/* Copyright (c) 2018 Albert S.
|
|
|
|
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
|
|
in the Software without restriction, including without limitation the rights
|
|
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
|
|
|
|
The above copyright notice and this permission notice shall be included in all
|
|
|
|
copies or substantial portions of the Software.
|
|
|
|
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
|
SOFTWARE.
|
|
|
|
*/
|
|
|
|
#include <regex>
|
|
|
|
#include <iostream>
|
|
|
|
#include <regex>
|
|
|
|
#include <vector>
|
|
|
|
#include <algorithm>
|
|
|
|
#include <iterator>
|
|
|
|
#include "parser.h"
|
|
|
|
#include "utils.h"
|
|
|
|
#include "htmllink.h"
|
2022-01-23 10:12:37 +01:00
|
|
|
std::vector<Headline> Parser::extractHeadlines(const std::string &content) const
|
2018-11-03 17:12:20 +01:00
|
|
|
{
|
|
|
|
std::vector<Headline> result;
|
2022-03-29 22:35:45 +02:00
|
|
|
|
2022-04-03 11:48:16 +02:00
|
|
|
std::string reg = R"(\[h(1|2|3)\](\[.*?\])*(.*?)(\[.*?\])*\[\/h\1\])";
|
2018-11-03 17:12:20 +01:00
|
|
|
std::regex headerfinder(reg);
|
|
|
|
auto begin = std::sregex_iterator(content.begin(), content.end(), headerfinder);
|
|
|
|
auto end = std::sregex_iterator();
|
|
|
|
|
|
|
|
for(auto it = begin; it != end; it++)
|
|
|
|
{
|
|
|
|
auto smatch = *it;
|
|
|
|
Headline h;
|
|
|
|
h.level = utils::toUInt(smatch.str(1));
|
2022-03-29 22:35:45 +02:00
|
|
|
h.title = smatch.str(3);
|
2018-11-03 17:12:20 +01:00
|
|
|
result.push_back(h);
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2022-01-23 10:12:37 +01:00
|
|
|
std::vector<std::string> Parser::extractCategories(const std::string &content) const
|
2018-11-03 17:12:20 +01:00
|
|
|
{
|
|
|
|
std::vector<std::string> result;
|
|
|
|
std::string reg = R"(\[category\](.*?)\[/category\])";
|
|
|
|
std::regex headerfinder(reg);
|
|
|
|
auto begin = std::sregex_iterator(content.begin(), content.end(), headerfinder);
|
|
|
|
auto end = std::sregex_iterator();
|
|
|
|
|
|
|
|
for(auto it = begin; it != end; it++)
|
|
|
|
{
|
|
|
|
auto smatch = *it;
|
|
|
|
result.emplace_back(smatch.str(1));
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
2018-11-04 19:33:06 +01:00
|
|
|
|
2023-11-27 21:37:54 +01:00
|
|
|
std::string Parser::extractFirstTag(std::string tagname, const std::string &content) const
|
2018-11-04 19:33:06 +01:00
|
|
|
{
|
2023-11-27 21:37:54 +01:00
|
|
|
std::string cmd = "[" + tagname + "]";
|
|
|
|
std::string cmdend = "[/" + tagname + "]";
|
2018-11-05 22:36:18 +01:00
|
|
|
std::string_view view = content;
|
|
|
|
size_t pos = 0;
|
|
|
|
if((pos = view.find(cmd)) != std::string::npos)
|
|
|
|
{
|
|
|
|
view.remove_prefix(pos);
|
|
|
|
view.remove_prefix(cmd.size());
|
|
|
|
if((pos = view.find(cmdend)) != std::string::npos)
|
2018-11-05 10:46:06 +01:00
|
|
|
{
|
2018-11-05 22:36:18 +01:00
|
|
|
auto result = view.substr(0, pos);
|
|
|
|
return std::string{result};
|
2018-11-05 10:46:06 +01:00
|
|
|
}
|
2018-11-04 19:33:06 +01:00
|
|
|
}
|
|
|
|
return "";
|
|
|
|
}
|
2023-07-28 15:04:58 +02:00
|
|
|
|
2023-11-27 21:37:54 +01:00
|
|
|
std::string Parser::extractCommand(std::string cmdname, const std::string &content) const
|
|
|
|
{
|
|
|
|
|
|
|
|
return extractFirstTag("cmd:" + cmdname, content);
|
|
|
|
}
|
|
|
|
|
2023-07-28 15:04:58 +02:00
|
|
|
std::vector<std::string> Parser::extractCommands(std::string cmdname, const std::string &content) const
|
|
|
|
{
|
|
|
|
std::vector<std::string> result;
|
|
|
|
|
|
|
|
std::string cmd = "[cmd:" + cmdname + "]";
|
|
|
|
std::string cmdend = "[/cmd:" + cmdname + "]";
|
|
|
|
|
|
|
|
std::string_view view = content;
|
|
|
|
size_t pos = 0;
|
|
|
|
while((pos = view.find(cmd)) != std::string::npos)
|
|
|
|
{
|
|
|
|
view.remove_prefix(pos);
|
|
|
|
view.remove_prefix(cmd.size());
|
|
|
|
if((pos = view.find(cmdend)) != std::string::npos)
|
|
|
|
{
|
|
|
|
result.emplace_back(view.substr(0, pos));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2020-03-18 22:00:15 +01:00
|
|
|
std::string Parser::processLink(const PageDao &pageDao, UrlProvider &urlProvider, std::smatch &match) const
|
2018-11-03 17:12:20 +01:00
|
|
|
{
|
|
|
|
std::string linktag = match.str(1);
|
|
|
|
std::string inside = match.str(2);
|
|
|
|
|
2021-04-17 12:41:22 +02:00
|
|
|
std::vector<std::string> splitted = utils::split(inside, '|');
|
2018-11-03 17:12:20 +01:00
|
|
|
HtmlLink htmllink;
|
|
|
|
if(splitted.size() == 2)
|
|
|
|
{
|
|
|
|
htmllink.innervalue = splitted[1];
|
|
|
|
htmllink.href = splitted[0];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
htmllink.innervalue = inside;
|
|
|
|
htmllink.href = inside;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(linktag == "wikilink")
|
|
|
|
{
|
|
|
|
if(pageDao.exists(htmllink.href))
|
|
|
|
{
|
|
|
|
htmllink.cssclass = "exists";
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
htmllink.cssclass = "notexists";
|
|
|
|
}
|
|
|
|
|
|
|
|
htmllink.href = urlProvider.page(htmllink.href);
|
|
|
|
}
|
|
|
|
|
|
|
|
return htmllink.render();
|
|
|
|
}
|
2018-11-04 19:33:06 +01:00
|
|
|
|
2022-04-19 19:50:22 +02:00
|
|
|
std::string Parser::processImage(std::smatch &match) const
|
|
|
|
{
|
|
|
|
std::string tag = match.str(1);
|
|
|
|
std::string inside = match.str(2);
|
|
|
|
std::vector<std::string> splitted = utils::split(inside, '|');
|
|
|
|
std::string width;
|
|
|
|
std::string height;
|
|
|
|
std::string src;
|
|
|
|
if(splitted.size() == 3)
|
|
|
|
{
|
|
|
|
width = splitted[0];
|
|
|
|
height = splitted[1];
|
|
|
|
src = splitted[2];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
src = splitted[0];
|
|
|
|
}
|
|
|
|
if(!width.empty() && !height.empty())
|
|
|
|
{
|
|
|
|
return "<img src=\"" + src + "\" width=\"" + width + "\" height=\"" + height + "\"/>";
|
|
|
|
}
|
|
|
|
return "<img src=\"" + src + "\"/>";
|
|
|
|
}
|
|
|
|
|
2022-03-27 08:31:59 +02:00
|
|
|
std::string Parser::parse(const PageDao &pagedao, UrlProvider &provider, const std::string &content,
|
|
|
|
const std::function<std::string(std::string_view, std::string_view)> &callback) const
|
2018-11-03 17:12:20 +01:00
|
|
|
{
|
|
|
|
std::string result;
|
2018-11-10 22:44:19 +01:00
|
|
|
// we don't care about commands, but we nevertheless replace them with empty strings
|
2022-01-23 10:02:46 +01:00
|
|
|
std::regex tagfinder(
|
2024-06-09 10:32:25 +02:00
|
|
|
R"(\[(b|i|u|s|li|p|br|ul|ol|code|blockquote|img|link|wikilink|h\d|cmd:visible|cmd:listed|cmd:feedlisted|cmd:rename|cmd:redirect|cmd:pagetitle|cmd:allowinclude|cmd:permissions|cmd:parentpage|content|category|dynamic:postlist|dynamic:includepage|dynamic:getvar|dynamic:setvar)*?\]((\s|\S)*?)\[/\1](\r\n)*)");
|
|
|
|
|
|
|
|
const std::string justreplace[] = {"b", "i", "u", "p", "br", "ul", "li", "ol"};
|
|
|
|
|
2022-01-23 10:02:46 +01:00
|
|
|
result = utils::regex_callback_replacer(
|
|
|
|
tagfinder, content,
|
|
|
|
[&](std::smatch &match)
|
2018-11-03 17:12:20 +01:00
|
|
|
{
|
2022-01-23 10:02:46 +01:00
|
|
|
std::string tag = match.str(1);
|
|
|
|
std::string content = match.str(2);
|
2024-06-09 10:32:25 +02:00
|
|
|
|
|
|
|
std::string newlines = match.str(4);
|
|
|
|
if(newlines == "\r\n")
|
|
|
|
{
|
|
|
|
newlines = "<br>";
|
|
|
|
}
|
2022-11-08 08:47:53 +01:00
|
|
|
if(tag != "code" && tag != "blockquote")
|
|
|
|
{
|
|
|
|
content = parse(pagedao, provider, content, callback);
|
|
|
|
}
|
2023-11-27 21:37:54 +01:00
|
|
|
/* [content] just helps extracting the actual content of a page, pretty much noop otherwise */
|
|
|
|
if(tag == "content")
|
|
|
|
{
|
|
|
|
return parse(pagedao, provider, content, callback);
|
|
|
|
}
|
2022-01-23 10:02:46 +01:00
|
|
|
if(std::find(std::begin(justreplace), std::end(justreplace), tag) != std::end(justreplace))
|
|
|
|
{
|
2024-06-09 10:32:25 +02:00
|
|
|
if(tag == "p" || tag == "br")
|
|
|
|
{
|
|
|
|
newlines = "";
|
|
|
|
}
|
|
|
|
return "<" + tag + ">" + content + "</" + tag + ">" + newlines;
|
2022-01-23 10:02:46 +01:00
|
|
|
}
|
|
|
|
if(tag == "link" || tag == "wikilink")
|
|
|
|
{
|
2024-06-09 10:32:25 +02:00
|
|
|
return this->processLink(pagedao, provider,
|
|
|
|
match) +
|
|
|
|
newlines; // TODO: recreate this so we don't check inside the function stuff again
|
2022-01-23 10:02:46 +01:00
|
|
|
}
|
2022-04-19 19:50:22 +02:00
|
|
|
if(tag == "img")
|
|
|
|
{
|
|
|
|
return this->processImage(match);
|
|
|
|
}
|
2022-01-23 10:02:46 +01:00
|
|
|
if(tag[0] == 'h')
|
|
|
|
{
|
|
|
|
return "<" + tag + " id='" + content + "'>" + content + "</" + tag + ">";
|
|
|
|
}
|
2022-11-08 08:47:53 +01:00
|
|
|
if(tag == "code" || tag == "blockquote")
|
|
|
|
{
|
2023-07-28 15:04:58 +02:00
|
|
|
return "<pre><" + tag + ">" + utils::strreplace(content, "\r\n", "\n") + "</" + tag + "></pre>";
|
2022-11-08 08:47:53 +01:00
|
|
|
}
|
2022-03-27 08:31:59 +02:00
|
|
|
return callback(tag, content);
|
2022-01-23 10:02:46 +01:00
|
|
|
});
|
2018-11-03 17:12:20 +01:00
|
|
|
result = utils::strreplace(result, "\r\n", "<br>");
|
|
|
|
return result;
|
|
|
|
}
|
2022-03-29 22:35:45 +02:00
|
|
|
|
|
|
|
std::string Parser::parseDynamics(const std::string &content,
|
|
|
|
const std::function<std::string(std::string_view, std::string_view)> &callback) const
|
|
|
|
{
|
|
|
|
std::regex tagfinder(R"(\[(dynamic:\w+)*?\]((\s|\S)*?)\[/\1])");
|
|
|
|
return utils::regex_callback_replacer(tagfinder, content,
|
|
|
|
[&](std::smatch &match) { return callback(match.str(1), match.str(2)); });
|
|
|
|
}
|