Files
core/HtmlFile2/Writers/MDWriter.cpp
Kirill Polyakov d1fada2542 Refactoring
2026-04-16 17:21:11 +03:00

444 lines
11 KiB
C++

#include "MDWriter.h"
#include <cwctype>
#include "../../DesktopEditor/common/File.h"
#include "../../Common/3dParty/html/css/src/CCompiledStyle.h"
namespace HTML
{
CMDWriter::CMDWriter(const TMarkdownParameters& oMDParametrs)
: m_oMDParametrs(oMDParametrs)
{
m_arStates.push(TState{});
m_arStates.top().m_pCurrentDocument = &m_oDocument;
}
void CMDWriter::Begin(const std::wstring& wsDst)
{}
void CMDWriter::End(const std::wstring& wsDst)
{
NSFile::CFileBinary oDocument;
if (!oDocument.CreateFileW(wsDst))
return;
oDocument.WriteStringUTF8(m_oDocument.GetData());
oDocument.CloseFile();
}
inline void ReplaceSpaces(std::wstring& wsValue)
{
// boost::wregex oRegex(L"\\s+");
// wsValue = boost::regex_replace(wsValue, oRegex, L" ");
std::wstring::const_iterator itBegin = std::find_if(wsValue.cbegin(), wsValue.cend(), [](wchar_t wchValue){ return std::iswspace(wchValue) && 0xa0 != wchValue; });
std::wstring::const_iterator itEnd;
while (wsValue.cend() != itBegin)
{
itEnd = std::find_if(itBegin, wsValue.cend(), [](wchar_t wchValue){ return !std::iswspace(wchValue) || 0xa0 == wchValue; });
wsValue.replace(itBegin, itEnd, L" ");
itBegin = std::find_if(itBegin + 1, wsValue.cend(), [](wchar_t wchValue){ return std::iswspace(wchValue) && 0xa0 != wchValue; });
}
}
void ReplaceSpecialCharsInEdges(std::wstring& wsText)
{
// Hexadecimal codes of the characters being replaced
const wchar_t SP {0x20}; // space
const wchar_t NBSP{0xA0}; // non-breaking space
const wchar_t ENSP{0x2002}; // en space
const wchar_t EMSP{0x2003}; // em space
// Corresponding HTML sequences
const std::wstring NBSP_REP{L"&nbsp;"};
const std::wstring ENSP_REP{L"&ensp;"};
const std::wstring EMSP_REP{L"&emsp;"};
auto is_special = [&](wchar_t c) -> bool {
return c == SP || c == NBSP || c == ENSP || c == EMSP;
};
size_t unFirstNormal{0};
while (unFirstNormal < wsText.size() && is_special(wsText[unFirstNormal]))
++unFirstNormal;
if (unFirstNormal == wsText.size())
{
std::wstring wsResult;
wsResult.reserve(wsText.size() * 6); // each character will be replaced by 6
for (wchar_t c : wsText)
{
if (c == SP || c == NBSP) wsResult.append(NBSP_REP);
else if (c == ENSP) wsResult.append(ENSP_REP);
else if (c == EMSP) wsResult.append(EMSP_REP);
else wsResult.push_back(c);
}
wsText = std::move(wsResult);
return;
}
size_t unLastNormal{wsText.size() - 1};
while (unLastNormal > unFirstNormal && is_special(wsText[unLastNormal]))
--unLastNormal;
const size_t unLeftCount {unFirstNormal};
const size_t unRightCount{wsText.size() - 1 - unLastNormal};
const size_t unNormalLen{unLastNormal - unFirstNormal + 1};
const size_t unNewLen {unNormalLen + unLeftCount * 6 + unRightCount * 6};
std::wstring wsResult;
wsResult.reserve(unNewLen);
for (size_t i = 0; i < unLeftCount; ++i)
{
wchar_t c = wsText[i];
if (c == SP || c == NBSP) wsResult.append(NBSP_REP);
else if (c == ENSP) wsResult.append(ENSP_REP);
else if (c == EMSP) wsResult.append(EMSP_REP);
}
for (size_t i = unFirstNormal; i <= unLastNormal; ++i)
wsResult.push_back(wsText[i]);
for (size_t i = unLastNormal + 1; i < wsText.size(); ++i)
{
wchar_t c = wsText[i];
if (c == SP || c == NBSP) wsResult.append(NBSP_REP);
else if (c == ENSP) wsResult.append(ENSP_REP);
else if (c == EMSP) wsResult.append(EMSP_REP);
}
wsText = std::move(wsResult);
}
bool CMDWriter::WriteText(std::wstring wsText, const std::vector<NSCSS::CNode>& arSelectors)
{
bool bPreformatted{InPreformatted()};
const NSCSS::CCompiledStyle* pCompiledStyle{arSelectors.back().m_pCompiledStyle};
if (!bPreformatted && nullptr != pCompiledStyle)
{
// TODO:: behavior should be slightly different (implement)
switch(pCompiledStyle->m_oDisplay.GetWhiteSpace().ToInt())
{
case NSCSS::NSProperties::EWhiteSpace::Pre:
case NSCSS::NSProperties::EWhiteSpace::Pre_Wrap:
case NSCSS::NSProperties::EWhiteSpace::Pre_Line:
bPreformatted = true;
default:
break;
}
}
if (!bPreformatted && wsText.end() == std::find_if_not(wsText.begin(), wsText.end(), [](wchar_t wchChar){ return iswspace(wchChar) && 0xa0 != wchChar;}))
return false;
if (bPreformatted && !m_arStates.top().m_bEmptyLine && !m_arStates.top().m_bInTable)
{
for (std::vector<NSCSS::CNode>::const_reverse_iterator itElement{arSelectors.crbegin()}; itElement < arSelectors.crend(); ++itElement)
{
if (L"pre" == itElement->m_wsName)
{
GetCurrentDocument()->AddCharSafe(L'\n');
break;
}
}
}
// For now works correctly only for text (need to check with other nodes)
if (m_arStates.top().m_bEmptyLine)
{
for (UINT unIndex = 0; unIndex < GetLevelBlockquote(); ++unIndex)
WriteString(L"> ", true);
}
const bool bOnlySpaces{wsText.end() == std::find_if_not(wsText.begin(), wsText.end(), [](wchar_t wchChar){ return iswspace(wchChar);})};
if (!bPreformatted && !InCode())
{
ReplaceSpaces(wsText);
ReplaceSpecialCharsInEdges(wsText);
}
bool bNeedBold {false},
bNeedItalic{false},
bNeedStrike{false};
if (nullptr != pCompiledStyle && !bOnlySpaces)
{
if (pCompiledStyle->m_oFont.Bold())
bNeedBold = true;
if (pCompiledStyle->m_oFont.Italic())
bNeedItalic = true;
if (pCompiledStyle->m_oText.LineThrough())
bNeedStrike = true;
}
if (bNeedBold)
WriteOpenSpecialString(L"**");
if (bNeedItalic)
WriteOpenSpecialString(L"*");
if (bNeedStrike)
WriteOpenSpecialString(L"~~");
ApplyAlternativeTags(pCompiledStyle);
WriteString(wsText);
ApplyAlternativeTags(pCompiledStyle, true);
if (bNeedBold)
WriteCloseSpecialString(L"**");
if (bNeedItalic)
WriteCloseSpecialString(L"*");
if (bNeedStrike)
WriteCloseSpecialString(L"~~");
if (L'\n' == wsText.back())
m_arStates.top().m_bNeedBreakLine = false;
return true;
}
void CMDWriter::WriteEmptyParagraph(bool bVahish, bool bInP)
{}
void CMDWriter::PageBreak()
{}
void CMDWriter::BeginBlock()
{
if (!m_arStates.top().m_bInList)
WriteBreakLine();
}
void CMDWriter::EndBlock(bool bAddBlock)
{
if (!m_arStates.top().m_bInTable)
WriteBreakLine();
}
void CMDWriter::SetDataOutput(XmlString* pOutputData)
{
SaveState();
m_arStates.top().m_pCurrentDocument = pOutputData;
}
void CMDWriter::RevertDataOutput()
{
RollBackState();
}
TMarkdownParameters CMDWriter::GetParametrs() const
{
return m_oMDParametrs;
}
void CMDWriter::WriteString(const std::wstring& wsString, bool bSpecialString)
{
GetCurrentDocument()->WriteString(wsString);
if (m_arStates.top().m_bEmptyLine)
m_arStates.top().m_bEmptyLine = wsString.empty();
if (!bSpecialString)
{
if (!m_arStates.top().m_oSpecialString.m_wsLastSpecialString.empty() &&
m_arStates.top().m_oSpecialString.m_bClosed)
m_arStates.top().m_oSpecialString.m_wsLastSpecialString.clear();
m_arStates.top().m_bNeedBreakLine = true;
}
}
void CMDWriter::WriteOpenSpecialString(const std::wstring& wsString)
{
if (!m_arStates.top().m_oSpecialString.m_wsLastSpecialString.empty() &&
(m_arStates.top().m_oSpecialString.m_wsLastSpecialString == wsString ||
(L'*' == m_arStates.top().m_oSpecialString.m_wsLastSpecialString.front() && L'*' == wsString.front())))
GetCurrentDocument()->WriteString(L" ");
m_arStates.top().m_oSpecialString.m_wsLastSpecialString.clear();
m_arStates.top().m_oSpecialString.m_bClosed = false;
WriteString(wsString, true);
}
void CMDWriter::WriteCloseSpecialString(const std::wstring& wsString)
{
m_arStates.top().m_oSpecialString.m_wsLastSpecialString = wsString;
m_arStates.top().m_oSpecialString.m_bClosed = true;
WriteString(wsString, true);
}
XmlString* CMDWriter::GetCurrentDocument() const
{
return m_arStates.top().m_pCurrentDocument;
}
void CMDWriter::WriteBreakLine(bool bNeedChecked)
{
if (bNeedChecked && !m_arStates.top().m_bNeedBreakLine)
return;
if (m_arStates.top().m_bInTable && bNeedChecked)
{
if (m_oMDParametrs.m_bUseAlternativeHTMLTags)
WriteString(L"</br>");
m_arStates.top().m_bNeedBreakLine = false;
return;
}
if (m_arStates.top().m_bEmptyLine)
{
for (UINT unIndex = 0; unIndex < GetLevelBlockquote(); ++unIndex)
WriteString(L"> ", true);
}
GetCurrentDocument()->WriteString(L" \n");
m_arStates.top().m_bEmptyLine = true;
m_arStates.top().m_bNeedBreakLine = false;
}
void CMDWriter::EnteredBlockquote()
{
m_arStates.top().m_unLevelBlockquote++;
}
void CMDWriter::OutBlockquote()
{
if (m_arStates.top().m_unLevelBlockquote > 0)
m_arStates.top().m_unLevelBlockquote--;
}
UINT CMDWriter::GetLevelBlockquote()
{
return m_arStates.top().m_unLevelBlockquote;
}
void CMDWriter::EnteredTable()
{
m_arStates.top().m_bInTable = true;
}
void CMDWriter::OutTable()
{
m_arStates.top().m_bInTable = false;
}
bool CMDWriter::InTable() const
{
return m_arStates.top().m_bInTable;
}
void CMDWriter::EnteredPreformatted()
{
m_arStates.top().m_bInPreformatted = true;
}
void CMDWriter::OutPreformatted()
{
m_arStates.top().m_bInPreformatted = false;
}
bool CMDWriter::InPreformatted() const
{
return m_arStates.top().m_bInPreformatted;
}
void CMDWriter::EnteredCode()
{
m_arStates.top().m_bInCode = true;
}
void CMDWriter::OutCode()
{
m_arStates.top().m_bInCode = false;
}
bool CMDWriter::InCode() const
{
return m_arStates.top().m_bInCode;
}
void CMDWriter::EnteredList(bool bOrderedList)
{
SaveState();
m_arStates.top().m_bInList = true;
m_arStates.top().m_bIsOrederedList = bOrderedList;
m_arStates.top().m_unLevelList++;
}
void CMDWriter::OutList()
{
RollBackState();
}
void CMDWriter::SetIndexOrderedList(UINT unIndex)
{
m_arStates.top().m_unIndexListElement = unIndex;
}
void CMDWriter::IncreaseIndexOrderedList()
{
m_arStates.top().m_unIndexListElement++;
}
bool CMDWriter::InOrederedList() const
{
return m_arStates.top().m_bIsOrederedList;
}
UINT CMDWriter::GetIndexOrderedList() const
{
return m_arStates.top().m_unIndexListElement;
}
UINT CMDWriter::GetLevelList() const
{
return m_arStates.top().m_unLevelList;
}
void CMDWriter::SaveState()
{
m_arStates.push(m_arStates.top());
}
void CMDWriter::RollBackState()
{
if (m_arStates.size() > 1)
m_arStates.pop();
}
void CMDWriter::ApplyAlternativeTags(const NSCSS::CCompiledStyle* pCompiledStyle, bool bIsCloseTag)
{
if (nullptr == pCompiledStyle || !m_oMDParametrs.m_bUseAlternativeHTMLTags)
return;
if (pCompiledStyle->m_oText.Underline())
GetCurrentDocument()->WriteString(bIsCloseTag ? L"</u>" : L"<u>");
if (L"top" == pCompiledStyle->m_oDisplay.GetVAlign().ToWString())
GetCurrentDocument()->WriteString(bIsCloseTag ? L"</sup>" : L"<sup>");
else if (L"bottom" == pCompiledStyle->m_oDisplay.GetVAlign().ToWString())
GetCurrentDocument()->WriteString(bIsCloseTag ? L"</sub>" : L"<sub>");
if (L"FFFF00" == pCompiledStyle->m_oBackground.GetColor().ToHEX())
GetCurrentDocument()->WriteString(bIsCloseTag ? L"</mark>" : L"<mark>");
}
}