# -*- coding: utf-8 -*- """novelWriter HTML Text Converter novelWriter – HTML Text Converter =================================== Extends the Tokenizer class to write HTML File History: Created: 2019-05-07 [0.0.1] This file is a part of novelWriter Copyright 2020, Veronica Berglyd Olsen This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """ import logging import re import nw from nw.core.tokenizer import Tokenizer from nw.constants import nwUnicode, nwLabels, nwKeyWords logger = logging.getLogger(__name__) class ToHtml(Tokenizer): M_PREVIEW = 0 # Tweak output for the DocViewer M_EXPORT = 1 # Tweak output for saving to HTML or printing M_EBOOK = 2 # Tweak output for converting to epub def __init__(self, theProject, theParent): Tokenizer.__init__(self, theProject, theParent) self.genMode = self.M_EXPORT self.repDict = { "<" : "<", ">" : ">", "&" : "&", "\t" : " ", nwUnicode.U_ENDASH : nwUnicode.H_ENDASH, nwUnicode.U_EMDASH : nwUnicode.H_EMDASH, nwUnicode.U_HELLIP : nwUnicode.H_HELLIP, nwUnicode.U_NBSP : nwUnicode.H_NBSP, } return ## # Setters ## def setPreview(self, forPreview, doComments): """If we're using this class to generate markdown preview, we need to make a few changes to formatting, which is managed by these flags. """ if forPreview: self.genMode = self.M_PREVIEW self.doKeywords = True self.doComments = doComments self.repDict["\t"] = " "*8 return ## # Class Methods ## def doAutoReplace(self): """Extend the auto-replace to also properly encode some unicode characters into their respective HTML entities. """ Tokenizer.doAutoReplace(self) xRep = re.compile("|".join([re.escape(k) for k in self.repDict.keys()]), flags=re.DOTALL) self.theText = xRep.sub(lambda x: self.repDict[x.group(0)], self.theText) return def doPostProcessing(self): """Reverse the html entities replacement on the markdown text. Otherwise, all the &something; bits will also be in there. """ if self.genMode == self.M_PREVIEW: # Doesn't matter for preview as we don't use the markdown return revDict = dict(map(reversed, self.repDict.items())) xRep = re.compile("|".join([re.escape(k) for k in revDict.keys()]), flags=re.DOTALL) self.theMarkdown = xRep.sub(lambda x: revDict[x.group(0)], self.theMarkdown) return def doConvert(self): """Convert the list of text tokens into a HTML document saved to theResult. """ htmlTags = { self.FMT_B_B : "", self.FMT_B_E : "", self.FMT_I_B : "", self.FMT_I_E : "", self.FMT_U_B : "", self.FMT_U_E : "", } self.theResult = "" thisPar = [] parStyle = "" tmpResult = [] for tType, tText, tFormat, tStyle in self.theTokens: # Styles aStyle = [] if tStyle is not None: if tStyle & self.A_LEFT: aStyle.append("text-align: left;") if tStyle & self.A_RIGHT: aStyle.append("text-align: right;") if tStyle & self.A_CENTRE: aStyle.append("text-align: center;") if tStyle & self.A_JUSTIFY: aStyle.append("text-align: justify;") if tStyle & self.A_PBB: aStyle.append("page-break-before: always;") if tStyle & self.A_PBB_L: aStyle.append("page-break-before: left;") if tStyle & self.A_PBB_R: aStyle.append("page-break-before: right;") if tStyle & self.A_PBB_AV: aStyle.append("page-break-before: avoid;") if tStyle & self.A_PBA: aStyle.append("page-break-after: always;") if tStyle & self.A_PBA_L: aStyle.append("page-break-after: left;") if tStyle & self.A_PBA_R: aStyle.append("page-break-after: right;") if tStyle & self.A_PBA_AV: aStyle.append("page-break-after: avoid;") if len(aStyle) > 0: hStyle = " style='%s'" % (" ".join(aStyle)) else: hStyle = "" # Process TextType if tType == self.T_EMPTY: if len(thisPar) > 0: tTemp = "".join(thisPar) tmpResult.append("%s

\n" % (parStyle, tTemp.rstrip())) thisPar = [] parStyle = "" elif tType == self.T_HEAD1: tHead = tText.replace(r"\\", "
") tmpResult.append("%s\n" % (hStyle, tHead)) elif tType == self.T_HEAD2: tHead = tText.replace(r"\\", "
") tmpResult.append("%s\n" % (hStyle, tHead)) elif tType == self.T_HEAD3: tHead = tText.replace(r"\\", "
") tmpResult.append("%s\n" % (hStyle, tHead)) elif tType == self.T_HEAD4: tHead = tText.replace(r"\\", "
") tmpResult.append("%s\n" % (hStyle, tHead)) elif tType == self.T_SEP: tmpResult.append("%s

\n" % (hStyle, tText)) elif tType == self.T_SKIP: tmpResult.append(" 

\n" % hStyle) elif tType == self.T_TEXT: tTemp = tText parStyle = hStyle for xPos, xLen, xFmt in reversed(tFormat): tTemp = tTemp[:xPos]+htmlTags[xFmt]+tTemp[xPos+xLen:] if tText.endswith(" "): thisPar.append(tTemp.rstrip()+"
") else: thisPar.append(tTemp.rstrip()+" ") elif tType == self.T_SYNOPSIS and self.doSynopsis: tmpResult.append(self._formatSynopsis(tText)) elif tType == self.T_COMMENT and self.doComments: tmpResult.append(self._formatComments(tText)) elif tType == self.T_KEYWORD and self.doKeywords: tmpResult.append(self._formatKeywords(tText)) self.theResult = "".join(tmpResult) tmpResult = [] return ## # Internal Functions ## def _formatSynopsis(self, tText): """Apply HTML formatting to synopsis. """ if self.genMode == self.M_EXPORT: return "

Synopsis: %s

\n" % tText else: return "

%s

\n" % tText def _formatComments(self, tText): """Apply HTML formatting to comments. """ if self.genMode == self.M_EXPORT: return "

Comment: %s

\n" % tText else: return "

%s

\n" % tText def _formatKeywords(self, tText): """Apply HTML formatting to keywords. """ tText = "@"+tText isValid, theBits, thePos = self.theParent.theIndex.scanThis(tText) if not isValid or not theBits: return "" retText = "" refTags = [] if theBits[0] in nwLabels.KEY_NAME: retText += "%s: " % nwLabels.KEY_NAME[theBits[0]] if self.genMode == self.M_PREVIEW: for tTag in theBits[1:]: refTags.append("%s" % ( theBits[0][1:], tTag, tTag )) retText += ", ".join(refTags) else: if theBits[0] == nwKeyWords.TAG_KEY: retText += "%s" % ( theBits[1], theBits[1] ) else: for tTag in theBits[1:]: refTags.append("%s" % ( tTag, tTag )) retText += ", ".join(refTags) return "
%s
" % retText # END Class ToHtml