Files
core/Common/3dParty/html/htmltoxhtml.h
Svetlana Kulikova 11affc304c changes
2020-09-08 15:15:20 +03:00

587 lines
20 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#ifndef HTMLTOXHTML_H
#define HTMLTOXHTML_H
#include <string>
#include <map>
#include <cctype>
#include "gumbo-parser/src/gumbo.h"
#include "../../../DesktopEditor/common/File.h"
#include "../../../DesktopEditor/common/Directory.h"
#include "../../../DesktopEditor/common/StringBuilder.h"
#include "../../../UnicodeConverter/UnicodeConverter.h"
static std::string nonbreaking_inline = "|a|abbr|acronym|b|bdo|big|cite|code|dfn|em|font|i|img|kbd|nobr|s|small|span|strike|strong|sub|sup|tt|";
static std::string empty_tags = "|area|base|basefont|bgsound|br|command|col|embed|event-source|frame|hr|image|img|input|keygen|link|menuitem|meta|param|source|spacer|track|wbr|";
static std::string preserve_whitespace = "|pre|textarea|script|style|";
static std::string special_handling = "|html|body|";
static std::string no_entity_sub = ""; //"|style|";
static std::string treat_like_inline = "|p|";
static void prettyprint(GumboNode*, NSStringUtils::CStringBuilderA& oBuilder);
static std::string mhtTohtml(std::string& sFileContent, const std::wstring& sTmp);
// Заменяет в строке s все символы s1 на s2
static void replace_all(std::string& s, std::string s1, std::string s2)
{
size_t len = s1.length();
size_t pos = s.find(s1);
while(pos != std::string::npos)
{
s.replace(pos, len, s2);
pos = s.find(s1, pos + len);
}
}
static std::wstring htmlToXhtml(const std::wstring& sFile)
{
std::string sFileContent;
if(!NSFile::CFileBinary::ReadAllTextUtf8A(sFile, sFileContent))
return L"";
// Распознование кодировки
size_t posEncoding = sFileContent.find("charset=");
if (posEncoding == std::string::npos)
posEncoding = sFileContent.find("encoding=");
if (posEncoding != std::string::npos)
{
posEncoding = sFileContent.find("=", posEncoding) + 1;
if(sFileContent[posEncoding] == '\"')
posEncoding += 1;
size_t posEnd = sFileContent.find("\"", posEncoding);
if (std::string::npos != posEnd)
{
std::string sEncoding = sFileContent.substr(posEncoding, posEnd - posEncoding);
if (sEncoding != "utf-8" && sEncoding != "UTF-8")
{
// sFileContent.replace(posEncoding, posEnd - posEncoding, "UTF-8");
NSUnicodeConverter::CUnicodeConverter oConverter;
sFileContent = U_TO_UTF8(oConverter.toUnicode(sFileContent, sEncoding.c_str()));
}
}
}
// Gumbo
GumboOptions options = kGumboDefaultOptions;
GumboOutput* output = gumbo_parse_with_options(&options, sFileContent.data(), sFileContent.length());
// prettyprint
NSStringUtils::CStringBuilderA oBuilder;
prettyprint(output->document, oBuilder);
std::string sR = oBuilder.GetData();
// Вставка кодировки в файл
/*
if(sR.length() > 5)
{
std::string sSub = sR.substr(0, 5);
if(sSub != "<?xml")
sR = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + sR;
}
*/
// Конвертирование из string utf8 в wstring
return UTF8_TO_U(sR);
}
static std::string Base64ToString(const std::string& sContent, const std::string& sCharset)
{
std::string sRes;
int nSrcLen = (int)sContent.length();
int nDecodeLen = NSBase64::Base64DecodeGetRequiredLength(nSrcLen);
BYTE* pData = new BYTE[nDecodeLen];
if (TRUE == NSBase64::Base64Decode(sContent.c_str(), nSrcLen, pData, &nDecodeLen))
{
std::wstring sConvert;
if(!sCharset.empty() && sCharset != "utf-8" && sCharset != "UTF-8")
{
NSUnicodeConverter::CUnicodeConverter oConverter;
sConvert = oConverter.toUnicode(reinterpret_cast<char *>(pData), (unsigned)nDecodeLen, sCharset.data());
}
sRes = sConvert.empty() ? std::string(reinterpret_cast<char *>(pData), nDecodeLen) : U_TO_UTF8(sConvert);
}
RELEASEARRAYOBJECTS(pData);
return sRes;
}
static std::string QuotedPrintableDecode(const std::string& sContent)
{
NSStringUtils::CStringBuilderA sRes;
size_t ip = 0;
size_t i = sContent.find('=');
size_t nLength = sContent.length();
while(i != std::string::npos && i + 2 < nLength)
{
sRes.WriteString(sContent.substr(ip, i - ip));
std::string str = sContent.substr(i + 1, 2);
char * err;
char ch = (int)strtol(str.data(), &err, 16);
if(*err)
{
if(str != "\r\n")
sRes.WriteString(sContent.substr(i, 3));
}
else
sRes.WriteString(&ch, 1);
ip = i + 3;
i = sContent.find('=', ip);
}
sRes.WriteString(sContent.substr(ip));
return sRes.GetData();
}
static void ReadMht(std::string& sFileContent, const std::wstring& sTmp, size_t& nFound, size_t& nNextFound, const std::string& sBoundary,
std::map<std::string, std::string>& sRes, NSStringUtils::CStringBuilderA& oRes)
{
// Content
size_t nContentTag = sFileContent.find("\n\n", nFound);
if(nContentTag == std::string::npos || nContentTag > nNextFound)
nContentTag = sFileContent.find("\n\r\n", nFound);
if(nContentTag == std::string::npos || nContentTag > nNextFound)
{
nFound = nNextFound;
return;
}
// Content-Type
size_t nTag = sFileContent.find("Content-Type: ", nFound);
if(nTag == std::string::npos || nTag > nContentTag)
{
nFound = nNextFound;
return;
}
size_t nTagEnd = sFileContent.find_first_of(";\n\r", nTag);
nTag += 14;
if(nTagEnd == std::string::npos || nTagEnd > nContentTag)
{
nFound = nNextFound;
return;
}
std::string sContentType = sFileContent.substr(nTag, nTagEnd - nTag);
if(sContentType == "multipart/alternative")
nContentTag = nFound;
// name
std::string sName;
nTag = sFileContent.find(" name=", nFound);
if(nTag != std::string::npos && nTag < nContentTag)
{
nTagEnd = sFileContent.find_first_of(";\n\r", nTag);
nTag += 6;
if(nTagEnd != std::string::npos && nTagEnd < nContentTag)
sName = sFileContent.substr(nTag, nTagEnd - nTag);
}
// charset
std::string sCharset;
nTag = sFileContent.find("charset=", nFound);
if(nTag != std::string::npos && nTag < nContentTag)
{
nTagEnd = sFileContent.find_first_of(";\n\r", nTag);
nTag += 8;
if(nTagEnd != std::string::npos && nTagEnd < nContentTag)
{
if(sFileContent[nTag] == '\"')
{
nTag++;
nTagEnd--;
}
sCharset = sFileContent.substr(nTag, nTagEnd - nTag);
}
}
// Content-Location
std::string sContentLocation;
nTag = sFileContent.find("Content-Location: ", nFound);
if(nTag != std::string::npos && nTag < nContentTag)
{
nTagEnd = sFileContent.find_first_of(";\n\r", nTag);
nTag += 18;
if(nTagEnd != std::string::npos && nTagEnd < nContentTag)
sContentLocation = sFileContent.substr(nTag, nTagEnd - nTag);
}
// Content-Transfer-Encoding
std::string sContentEncoding;
nTag = sFileContent.find("Content-Transfer-Encoding: ", nFound);
if(nTag != std::string::npos && nTag < nContentTag)
{
nTagEnd = sFileContent.find_first_of(";\n\r", nTag);
nTag += 27;
if(nTagEnd != std::string::npos && nTagEnd < nContentTag)
sContentEncoding = sFileContent.substr(nTag, nTagEnd - nTag);
}
// Content
nTagEnd = nNextFound - 2;
nContentTag += 2;
if(nTagEnd == std::string::npos || nTagEnd < nContentTag)
{
nFound = nNextFound;
return;
}
std::string sContent = sFileContent.substr(nContentTag, nTagEnd - nContentTag);
// Удаляем лишнее
sFileContent.erase(0, nNextFound);
nFound = sFileContent.find(sBoundary);
std::wstring sExtention = NSFile::GetFileExtention(UTF8_TO_U(sName));
std::transform(sExtention.begin(), sExtention.end(), sExtention.begin(), tolower);
// Основной документ
if(sContentType == "multipart/alternative")
oRes.WriteString(mhtTohtml(sContent, sTmp));
else if((sContentType.find("text") != std::string::npos && (sExtention.empty() || sExtention == L"htm" || sExtention == L"html" || sExtention
== L"xhtml" || sExtention == L"css")) || (sContentType == "application/octet-stream" && (sContentLocation.find("css") !=
std::string::npos)))
{
// Стили заключаются в тэг <style>
if(sContentType == "text/css" || sExtention == L"css" || sContentLocation.find("css") != std::string::npos)
oRes.WriteString("<style>");
if(sContentEncoding == "Base64" || sContentEncoding == "base64")
oRes.WriteString(Base64ToString(sContent, sCharset));
else if(sContentEncoding == "8bit" || sContentEncoding == "7bit" || sContentEncoding.empty())
{
if (sCharset != "utf-8" && sCharset != "UTF-8" && !sCharset.empty())
{
NSUnicodeConverter::CUnicodeConverter oConverter;
sContent = U_TO_UTF8(oConverter.toUnicode(sContent, sCharset.data()));
}
oRes.WriteString(sContent);
}
else if(sContentEncoding == "quoted-printable" || sContentEncoding == "Quoted-Printable")
{
sContent = QuotedPrintableDecode(sContent);
if (sCharset != "utf-8" && sCharset != "UTF-8" && !sCharset.empty())
{
NSUnicodeConverter::CUnicodeConverter oConverter;
sContent = U_TO_UTF8(oConverter.toUnicode(sContent, sCharset.data()));
}
oRes.WriteString(sContent);
}
if(sContentType == "text/css" || sExtention == L"css" || sContentLocation.find("css") != std::string::npos)
oRes.WriteString("</style>");
}
// Картинки
else if((sContentType.find("image") != std::string::npos || sExtention == L"gif" || sContentType == "application/octet-stream") &&
(sContentEncoding == "Base64" || sContentEncoding == "base64"))
{
if(sExtention == L"ico" || sContentType.find("ico") != std::string::npos)
{
if(!sName.empty())
sName = sName.substr(0, sName.rfind('.')) + ".jpg";
else
sContentType = "image/jpg";
}
int nSrcLen = (int)sContent.length();
int nDecodeLen = NSBase64::Base64DecodeGetRequiredLength(nSrcLen);
BYTE* pData = new BYTE[nDecodeLen];
if (TRUE == NSBase64::Base64Decode(sContent.c_str(), nSrcLen, pData, &nDecodeLen))
{
if(!sName.empty())
{
NSFile::CFileBinary oWriter;
std::wstring sFolder = sTmp + L"/word/media/" + UTF8_TO_U(sName);
if(oWriter.CreateFileW(sFolder))
{
oWriter.WriteFile(pData, (DWORD)nDecodeLen);
oWriter.CloseFile();
}
sRes.insert(std::make_pair(sContentLocation, U_TO_UTF8(sFolder)));
}
else
sRes.insert(std::make_pair(sContentLocation, "data:" + sContentType + ";base64," + sContent));
}
RELEASEARRAYOBJECTS(pData);
}
}
static std::string mhtTohtml(std::string& sFileContent, const std::wstring& sTmp)
{
std::map<std::string, std::string> sRes;
NSStringUtils::CStringBuilderA oRes;
// Поиск boundary
size_t nFound = sFileContent.find("boundary=");
if(nFound == std::string::npos)
{
size_t nFoundEnd = sFileContent.length();
nFound = 0;
ReadMht(sFileContent, sTmp, nFound, nFoundEnd, "no", sRes, oRes);
return oRes.GetData();
}
size_t nFoundEnd = sFileContent.find_first_of(";\n\r", nFound);
if(nFoundEnd == std::string::npos)
return "";
nFound += 9;
if(sFileContent[nFound] == '\"')
{
nFound++;
nFoundEnd--;
}
if(nFound > nFoundEnd)
return "";
std::string sBoundary = sFileContent.substr(nFound, nFoundEnd - nFound);
size_t nBoundaryLength = sBoundary.length();
// Удаляем лишнее
nFound = sFileContent.find(sBoundary, nFoundEnd);
sFileContent.erase(0, nFound);
// Цикл по boundary
nFound = 0;
while(nFound != std::string::npos)
{
// Выход по --boundary--
if(sFileContent[nFound + nBoundaryLength + 1] == '-')
break;
nFoundEnd = sFileContent.find(sBoundary, nFound + nBoundaryLength);
if(nFoundEnd == std::string::npos)
break;
ReadMht(sFileContent, sTmp, nFound, nFoundEnd, sBoundary, sRes, oRes);
}
std::string sFile = oRes.GetData();
for(auto& item : sRes)
{
std::string sName = item.first;
size_t found = sFile.find(sName);
size_t sfound = sName.rfind('/');
if(found == std::string::npos && sfound != std::string::npos)
found = sFile.find(sName.erase(0, sfound + 1));
while(found != std::string::npos)
{
size_t fq = sFile.find_last_of("\"\'>=", found);
char ch = sFile[fq];
if(ch != '\"' && ch != '\'')
fq++;
size_t tq = sFile.find_first_of("\"\'<> ", found) + 1;
if(sFile[tq] != '\"' && sFile[tq] != '\'')
tq--;
if(ch != '>')
{
std::string is = item.second;
is = '\"' + is + '\"';
sFile.replace(fq, tq - fq, is);
found = sFile.find(sName, fq + is.length());
}
else
found = sFile.find(sName, tq);
}
}
return sFile;
}
static std::wstring mhtToXhtml(const std::wstring& sFile, const std::wstring& sTmp)
{
std::wstring sRes;
std::string sFileContent;
if(!NSFile::CFileBinary::ReadAllTextUtf8A(sFile, sFileContent))
return sRes;
NSDirectory::CreateDirectory(sTmp + L"/word");
NSDirectory::CreateDirectory(sTmp + L"/word/media");
sFileContent = mhtTohtml(sFileContent, sTmp);
// Gumbo
GumboOptions options = kGumboDefaultOptions;
GumboOutput* output = gumbo_parse_with_options(&options, sFileContent.data(), sFileContent.length());
// prettyprint
NSStringUtils::CStringBuilderA oBuilder;
prettyprint(output->document, oBuilder);
std::string sR = oBuilder.GetData();
// Вставка кодировки в файл
/*
if(sR.length() > 5)
{
std::string sSub = sR.substr(0, 5);
if(sSub != "<?xml")
sR = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + sR;
}
*/
// Конвертирование из string utf8 в wstring
return UTF8_TO_U(sR);
}
// Заменяет сущности &,<,> в text
static void substitute_xml_entities_into_text(std::string &text)
{
// replacing & must come first
replace_all(text, "&", "&amp;");
replace_all(text, "<", "&lt;");
replace_all(text, ">", "&gt;");
}
// Заменяет сущности " в text
static void substitute_xml_entities_into_attributes(std::string &text)
{
substitute_xml_entities_into_text(text);
replace_all(text, "\"", "&quot;");
}
static std::string handle_unknown_tag(GumboStringPiece* text)
{
if (text->data == NULL)
return "";
GumboStringPiece gsp = *text;
gumbo_tag_from_original_text(&gsp);
std::string sAtr = std::string(gsp.data, gsp.length);
size_t found = sAtr.find_first_of("-'+,./:=?;!*#@$_%<>&;\"\'()[]{}");
while(found != std::string::npos)
{
sAtr.erase(found, 1);
found = sAtr.find_first_of("-'+,./:=?;!*#@$_%<>&;\"\'()[]{}", found);
}
return sAtr;
}
static std::string get_tag_name(GumboNode* node)
{
std::string tagname = (node->type == GUMBO_NODE_DOCUMENT ? "document" : gumbo_normalized_tagname(node->v.element.tag));
if (tagname.empty())
tagname = handle_unknown_tag(&node->v.element.original_tag);
return tagname;
}
static void build_doctype(GumboNode* node, NSStringUtils::CStringBuilderA& oBuilder)
{
if (node->v.document.has_doctype)
{
oBuilder.WriteString("<!DOCTYPE ");
oBuilder.WriteString(node->v.document.name);
std::string pi(node->v.document.public_identifier);
if ((node->v.document.public_identifier != NULL) && !pi.empty())
{
oBuilder.WriteString(" PUBLIC \"");
oBuilder.WriteString(pi);
oBuilder.WriteString("\" \"");
oBuilder.WriteString(node->v.document.system_identifier);
oBuilder.WriteString("\"");
}
oBuilder.WriteString(">");
}
}
static void build_attributes(GumboAttribute* at, bool no_entities, NSStringUtils::CStringBuilderA& atts)
{
std::string sVal(at->value);
std::string sName(at->name);
atts.WriteString(" ");
if(sName.empty())
return;
size_t nBad = sName.find_first_of("-'+,.:=?#%<>&;\"\'()[]{}");
while(nBad != std::string::npos)
{
sName.erase(nBad, 1);
nBad = sName.find_first_of("-'+,.:=?#%<>&;\"\'()[]{}", nBad);
if(sName.empty())
return;
}
while(sName.front() >= '0' && sName.front() <= '9')
{
sName.erase(0, 1);
if(sName.empty())
return;
}
atts.WriteString(sName);
// determine original quote character used if it exists
std::string qs ="\"";
atts.WriteString("=");
atts.WriteString(qs);
if(!no_entities)
substitute_xml_entities_into_attributes(sVal);
atts.WriteString(sVal);
atts.WriteString(qs);
}
static void prettyprint_contents(GumboNode* node, NSStringUtils::CStringBuilderA& contents)
{
std::string key = "|" + get_tag_name(node) + "|";
bool no_entity_substitution = no_entity_sub.find(key) != std::string::npos;
bool keep_whitespace = preserve_whitespace.find(key) != std::string::npos;
bool is_inline = nonbreaking_inline.find(key) != std::string::npos;
GumboVector* children = &node->v.element.children;
for (size_t i = 0; i < children->length; i++)
{
GumboNode* child = static_cast<GumboNode*> (children->data[i]);
if (child->type == GUMBO_NODE_TEXT)
{
std::string val(child->v.text.text);
if(!no_entity_substitution)
substitute_xml_entities_into_text(val);
// Избавление от FF
size_t found = val.find_first_of("\014");
while(found != std::string::npos)
{
val.erase(found, 1);
found = val.find_first_of("\014", found);
}
contents.WriteString(val);
}
else if ((child->type == GUMBO_NODE_ELEMENT) || (child->type == GUMBO_NODE_TEMPLATE))
prettyprint(child, contents);
else if (child->type == GUMBO_NODE_WHITESPACE)
{
if (keep_whitespace || is_inline)
contents.WriteString(child->v.text.text);
}
else if (child->type != GUMBO_NODE_COMMENT)
{
// Сообщение об ошибке
// Does this actually exist: (child->type == GUMBO_NODE_CDATA)
// fprintf(stderr, "unknown element of type: %d\n", child->type);
}
}
}
static void prettyprint(GumboNode* node, NSStringUtils::CStringBuilderA& oBuilder)
{
// special case the document node
if (node->type == GUMBO_NODE_DOCUMENT)
{
build_doctype(node, oBuilder);
prettyprint_contents(node, oBuilder);
return;
}
std::string close = "";
std::string closeTag = "";
std::string tagname = get_tag_name(node);
std::string key = "|" + tagname + "|";
bool is_empty_tag = empty_tags.find(key) != std::string::npos;
bool no_entity_substitution = no_entity_sub.find(key) != std::string::npos;
// determine closing tag type
if (is_empty_tag)
close = "/";
else
closeTag = "</" + tagname + ">";
// build results
oBuilder.WriteString("<" + tagname);
// build attr string
const GumboVector * attribs = &node->v.element.attributes;
for (size_t i = 0; i < attribs->length; ++i)
{
GumboAttribute* at = static_cast<GumboAttribute*>(attribs->data[i]);
build_attributes(at, no_entity_substitution, oBuilder);
}
oBuilder.WriteString(close + ">");
// prettyprint your contents
prettyprint_contents(node, oBuilder);
oBuilder.WriteString(closeTag);
}
#endif // HTMLTOXHTML_H