std::string_view ast_node::unescaped_view()

in src/SyntaxTree.cpp [49:184]


std::string_view ast_node::unescaped_view() const
{
	if (!_unescaped)
	{
		if (is_type<block_quote_content_lines>())
		{
			// Trim leading and trailing empty lines
			const auto isNonEmptyLine = [](const std::unique_ptr<ast_node>& child) noexcept {
				return child->is_type<block_quote_line>();
			};
			const auto itrEndRev = std::make_reverse_iterator(
				std::find_if(children.cbegin(), children.cend(), isNonEmptyLine));
			const auto itrRev = std::find_if(children.crbegin(), itrEndRev, isNonEmptyLine);
			std::vector<std::optional<std::pair<std::string_view, std::string_view>>> lines(
				std::distance(itrRev, itrEndRev));

			std::transform(itrRev,
				itrEndRev,
				lines.rbegin(),
				[](const std::unique_ptr<ast_node>& child) noexcept {
					return (child->is_type<block_quote_line>() && !child->children.empty()
							   && child->children.front()->is_type<block_quote_empty_line>()
							   && child->children.back()->is_type<block_quote_line_content>())
						? std::make_optional(std::make_pair(child->children.front()->string_view(),
							child->children.back()->unescaped_view()))
						: std::nullopt;
				});

			// Calculate the common indent
			const auto commonIndent = std::accumulate(lines.cbegin(),
				lines.cend(),
				std::optional<size_t> {},
				[](auto value, const auto& line) noexcept {
					if (line)
					{
						const auto indent = line->first.size();

						if (!value || indent < *value)
						{
							value = indent;
						}
					}

					return value;
				});

			const auto trimIndent = commonIndent ? *commonIndent : 0;
			std::string joined;

			if (!lines.empty())
			{
				joined.reserve(std::accumulate(lines.cbegin(),
								   lines.cend(),
								   size_t {},
								   [trimIndent](auto value, const auto& line) noexcept {
									   if (line)
									   {
										   value += line->first.size() - trimIndent;
										   value += line->second.size();
									   }

									   return value;
								   })
					+ lines.size() - 1);

				bool firstLine = true;

				for (const auto& line : lines)
				{
					if (!firstLine)
					{
						joined.append(1, '\n');
					}

					if (line)
					{
						joined.append(line->first.substr(trimIndent));
						joined.append(line->second);
					}

					firstLine = false;
				}
			}

			const_cast<ast_node*>(this)->_unescaped =
				std::make_unique<unescaped_t>(std::move(joined));
		}
		else if (children.size() > 1)
		{
			std::string joined;

			joined.reserve(std::accumulate(children.cbegin(),
				children.cend(),
				size_t(0),
				[](size_t total, const std::unique_ptr<ast_node>& child) {
					return total + child->string_view().size();
				}));

			for (const auto& child : children)
			{
				joined.append(child->string_view());
			}

			const_cast<ast_node*>(this)->_unescaped =
				std::make_unique<unescaped_t>(std::move(joined));
		}
		else if (!children.empty())
		{
			const_cast<ast_node*>(this)->_unescaped =
				std::make_unique<unescaped_t>(children.front()->string_view());
		}
		else if (has_content() && is_type<escaped_unicode>())
		{
			const auto content = string_view();
			memory_input<> in(content.data(), content.size(), "escaped unicode");
			std::string utf8;

			utf8.reserve((content.size() + 1) / 2);
			unescape::unescape_j::apply(in, utf8);

			const_cast<ast_node*>(this)->_unescaped =
				std::make_unique<unescaped_t>(std::move(utf8));
		}
		else
		{
			const_cast<ast_node*>(this)->_unescaped =
				std::make_unique<unescaped_t>(std::string_view {});
		}
	}

	return std::visit(
		[](const auto& value) noexcept {
			return std::string_view { value };
		},
		*_unescaped);
}