Merge pull request #73 from vkbo/export_options

LaTeX Export
This commit is contained in:
Veronica K. Berglyd Olsen
2019-10-27 15:47:25 +01:00
committed by GitHub
24 changed files with 740 additions and 277 deletions
+2 -2
View File
@@ -48,7 +48,7 @@ templates_path = ["_templates"]
# You can specify multiple suffix as a list of string:
#
# source_suffix = [".rst", ".md"]
source_suffix = ".rst"
source_suffix = ".txt"
# The master toctree document.
master_doc = "index"
@@ -150,7 +150,7 @@ man_pages = [
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, "novelWriter", "novelWriter Documentation",
author, "novelWriter", "One line description of project.",
author, "novelWriter", "Markdown-like editor for novels.",
"Miscellaneous"),
]
+91
View File
@@ -0,0 +1,91 @@
##################
Exporting Projects
##################
The novelWriter project can be exported in various formats using the export tool available from :menuselection:`Project --> Export Project` or by pressing :kbd:`F5`.
**************
File Selection
**************
Which files are selected for export can be controlled from the "Selection" section.
The check box for "Novel files" will select any file that isn't classified as a note.
This is useful when exporting a document containing the novel itself.
It is also possible to select the "Note files" for export, with or without the "Novel files".
Comments can also optionally be included in the export for those formats that supports this.
**************
Export Formats
**************
Currently, five formats are supported for exporting.
novelWriter Markdown
====================
This is simply a concatenation of the files selected by the filters.
The files in the project are stacked together in the order they appear in the tree view, with comments, tags, etc. included.
This is a useful format for exporting the project for later import back into novelWriter.
Plain Text
==========
The plain text export format writes a simple ``.txt`` file without any formatting at all.
Word-wrapping to a fixed column width is supported.
It does, however, respect the centering of text if the :guilabel:`Fixed width` setting is larger than ``0``.
Markdown
========
The markdown export formats writes a file using the valid markdown formats used by novelWriter, and converts comments to preformatted blocks by indenting it with four spaces.
Word-wrapping to a fixed column width is supported.
HTML5 Document
==============
The html5 export format writes a single ``.htm`` file with a minimal style settings block.
The style setting block sets a maximum page width of ``768px``.
Comments are exported as indented text boxes with a light yellow background.
LaTeX Document
==============
The latex export formats generats a simple ``.tex`` file using a ``report`` document format.
All chapters and sections are exported as unnumbered, and the internal header numbering formatting of novelWriter is used instead.
Word-wrapping to a fixed column width in the LaTeX source is supported.
*****************
Header Formatting
*****************
The chapter, scene and section headers for novel files can receive some special treatment using the formats under :guilabel:`Chapter Headings` and :guilabel:`Other Headings`.
The chapters can either be of numbered or unnumbered type, the latter being suitable for prologues, epilogues, interludes, etc.
The headers can be generated using free text in combination with replace tags.
The tooltip will explain which tags are available for each heading type.
.. note::
Header formatting only applies to novel files.
Headings in note files will will be left as-is, but heading levels 1 through 4 are converted to the correct heading level in the respective output formats.
Numbered Chapters
=================
Numbered chapters can be automatically assigned a number from 1 and upwards.
The number is inserted in place of the ``%num%`` tag.
Alternatively, the number can be translated to a word by using the ``%numword%`` tag.
The word numbers feature is currently only supported in English, and works for the range from "One" to "Nine Hundred and Ninety-Nine".
Unnumbered Chapters
===================
Unnumbered chapters only support the ``%title%`` tag for auto-replacement.
Scenes and Sections
===================
Both scenes and sections support the ``%title%`` tag.
In addition, scenes behave differently than the other headings when the format does not contain any auto-replace tags.
If the text in the box is any static text, the heading formatter will treat it as a scene separator, and insert that text as centred text between scenes, and ignoring the first scene of a chapter if the chapter heading is not followed by any text.
Leaving these fields blank will disable any output of scene and section headers or separators to the exported file.
@@ -1,10 +1,12 @@
####################################
Welcome to novelWriter Documentation
====================================
####################################
This is the documentation for novelWriter |version|.
********
Contents
^^^^^^^^
********
.. toctree::
:maxdepth: 2
@@ -14,10 +16,12 @@ Contents
projects
structure
notes
export
technical
******************
Indices and Tables
^^^^^^^^^^^^^^^^^^
******************
* :ref:`genindex`
* :ref:`modindex`
-77
View File
@@ -1,77 +0,0 @@
User Interface
==============
The user interface is kept as simple as possible to avoid distractions.
The main window contains a tree vew pane with the entire structure of the project, and a small details panel below it to display additional information.
Editing a document can be done by either double-clicking on it, or hitting the return key when the item is selected.
This will open the source editor which uses a simplified markdown format described in the section below.
The document can also be viewed as html with all the comments and commands stripped out. To view a document, simply press Ctrl+R or select a file and go to :menuselection:`Document --> View Document` in the menu.
The document viewed does not need to be the same document currently being edited.
Markdown Format
^^^^^^^^^^^^^^^
the document editor uses a simplified markdown format.
That is, it supports basic formatting like bold, italics and underline, as well as four levels of headings.
The formats are listed below.
In addition to these standard markdown features, the editor also allows for comments, that is text that is ignored by the word counter and not exported or seen in the document viewer.
The editor also has a minimal set of commands used for setting tags and references between files.
* ``# Title``: Heading level one. The space after the # is mandatory.
* ``## Title``: Heading level two. The space after the # is mandatory.
* ``### Title``: Heading level three. The space after the # is mandatory.
* ``#### Title``: Heading level four. The space after the # is mandatory.
* ``**text**``: The text is renderred as bold text.
* ``_text_``:The text is renderred as italics text.
* ``__text__``: The text is renderred as underlined text.
* ``% text...``: A comment. The text is not exported, seen in viewer, or counted towards word counts.
* ``@keyword: value``: A keyword argument followed by a value, or a comma separated list of values.
Keyboard Shortcuts
^^^^^^^^^^^^^^^^^^
All features are available as keyboard shortcuts.
These are as following:
* ``Ctrl+Shift+O``: Open a project.
* ``Ctrl+Shift+S``: Save the current project.
* ``Ctrl+Shift+W``: Close the current project.
* ``Ctrl+Shift+,``: Change project settings.
* ``Ctrl+Shift+N``: Create new folder.
* ``Ctrl+E, F2``: If in tree view, edit a document or folder settings.
* ``Ctrl+Del``: If in tree view, move a document to trash, or delete a folder.
* ``Ctrl+Q``: Exit novelWriter.
* ``Ctrl+N``: Create new document.
* ``Ctrl+O``: Open selected document.
* ``Return``: If in tree view, open a document for editing.
* ``Ctrl+S``: Save the current document in the editor.
* ``Ctrl+W``: Close the current document in the editor.
* ``Ctrl+R``: If in tree view, open a document for viewing. If in editor pane, open current document for viewing.
* ``Ctrl+Shift+R``: Close the document view pane.
* ``Ctrl+Z``: Undo latest changes.
* ``Ctrl+Y``: Redo latest undo.
* ``Ctrl+C``: Copy selected text to clipboard.
* ``Ctrl+X``: Cut selected text to clipboard.
* ``Ctrl+V``: Paste text from clipboard to cursor position.
* ``Ctrl+A``: Select all text in document.
* ``Ctrl+Shift+A``: Select all text in current paragraph.
* ``Ctrl+1``: Switch focus to tree view pane.
* ``Ctrl+2``: Switch focus to document editor pane.
* ``Ctrl+3``: Switch focus to document viewer pane.
* ``Ctrl+T``: Show project timeline.
* ``Ctrl+B``: Format selected text, or word under cursor, as bold.
* ``Ctrl+I``: Format selected text, or word under cursor, as italic.
* ``Ctrl+U``: Format selected text, or word under cursor, as underline.
* ``Ctrl+D``: Wrap selected text, or word under cursor, in double quotes.
* ``Ctrl+Shift+D``: Wrap selected text, or word under cursor, in single quotes.
* ``Ctrl+Shift+Up``: Move item one step up in the tree view.
* ``Ctrl+Shift+Down``: Move item one step down in the tree view.
* ``Ctrl+F7``: Toggle spell checking.
* ``F7``: Re-run spell checker.
* ``Ctrl+.``: Correct word under cursor.
* ``F9``: Re-build project indices.
* ``F1``: Open documentation.
+95
View File
@@ -0,0 +1,95 @@
##############
User Interface
##############
The user interface is kept as simple as possible to avoid distractions.
The main window contains a tree vew pane with the entire structure of the project, and a small details panel below it to display additional information.
Editing a document can be done by either double-clicking on it, or hitting the return key when the item is selected.
This will open the source editor which uses a simplified markdown format described in the section below.
The document can also be viewed as html with all the comments and commands stripped out. To view a document, simply press Ctrl+R or select a file and go to :menuselection:`Document --> View Document` in the menu.
The document viewed does not need to be the same document currently being edited.
***************
Markdown Format
***************
the document editor uses a simplified markdown format.
That is, it supports basic formatting like bold, italics and underline, as well as four levels of headings.
The formats are listed below.
In addition to these standard markdown features, the editor also allows for comments, that is text that is ignored by the word counter and not exported or seen in the document viewer.
The editor also has a minimal set of commands used for setting tags and references between files.
.. csv-table:: Formatting Syntax
:header: "Format", "Description"
:widths: 15, 70
"``# Title``", "Heading level one. The space after the # is mandatory."
"``## Title``", "Heading level two. The space after the # is mandatory."
"``### Title``", "Heading level three. The space after the # is mandatory."
"``#### Title``", "Heading level four. The space after the # is mandatory."
"``**text**``", "The text is renderred as bold text."
"``_text_``", "The text is renderred as italics text."
"``__text__``", "The text is renderred as underlined text."
"``% text...``", "A comment. The text is not exported, seen in viewer, or counted towards word counts."
"``@keyword: value``", "A keyword argument followed by a value, or a comma separated list of values."
******************
Keyboard Shortcuts
******************
All features are available as keyboard shortcuts.
These are as following:
.. csv-table:: Keyboard Shortcuts
:header: "Shortcut", "Description"
:widths: 15, 70
":kbd:`Ctrl-.`", "Correct word under cursor."
":kbd:`Ctrl-1`", "Switch focus to tree view pane."
":kbd:`Ctrl-2`", "Switch focus to document editor pane."
":kbd:`Ctrl-3`", "Switch focus to document viewer pane."
":kbd:`Ctrl-A`", "Select all text in document."
":kbd:`Ctrl-B`", "Format selected text, or word under cursor, as bold."
":kbd:`Ctrl-C`", "Copy selected text to clipboard."
":kbd:`Ctrl-D`", "Wrap selected text, or word under cursor, in double quotes."
":kbd:`Ctrl-E`", "If in tree view, edit a document or folder settings."
":kbd:`Ctrl-F`", "Open the search bar and search for selected word, if any is selected."
":kbd:`Ctrl-H`", "Open the search and replace bar and search for selected word, if any is selected."
":kbd:`Ctrl-I`", "Format selected text, or word under cursor, as italic."
":kbd:`Ctrl-N`", "Create new document."
":kbd:`Ctrl-O`", "Open selected document."
":kbd:`Ctrl-Q`", "Exit novelWriter."
":kbd:`Ctrl-R`", "If in tree view, open a document for viewing. If in editor pane, open current document for viewing."
":kbd:`Ctrl-S`", "Save the current document in the editor."
":kbd:`Ctrl-T`", "Show project timeline."
":kbd:`Ctrl-U`", "Format selected text, or word under cursor, as underline."
":kbd:`Ctrl-V`", "Paste text from clipboard to cursor position."
":kbd:`Ctrl-W`", "Close the current document in the editor."
":kbd:`Ctrl-X`", "Cut selected text to clipboard."
":kbd:`Ctrl-Y`", "Redo latest undo."
":kbd:`Ctrl-Z`", "Undo latest changes."
":kbd:`Ctrl-F7`", "Toggle spell checking."
":kbd:`Ctrl-Del`", "If in tree view, move a document to trash, or delete a folder."
":kbd:`Ctrl-Shift-,`", "Change project settings."
":kbd:`Ctrl-Shift-1`", "Replace occurrence of word in current document, and search for next occurrence."
":kbd:`Ctrl-Shift-A`", "Select all text in current paragraph."
":kbd:`Ctrl-Shift-D`", "Wrap selected text, or word under cursor, in single quotes."
":kbd:`Ctrl-Shift-I`", "Import text to the current document from a text file."
":kbd:`Ctrl-Shift-N`", "Create new folder."
":kbd:`Ctrl-Shift-O`", "Open a project."
":kbd:`Ctrl-Shift-R`", "Close the document view pane."
":kbd:`Ctrl-Shift-S`", "Save the current project."
":kbd:`Ctrl-Shift-W`", "Close the current project."
":kbd:`Ctrl-Shift-Up`", "Move item one step up in the tree view."
":kbd:`Ctrl-Shift-Down`", "Move item one step down in the tree view."
":kbd:`F1`", "Open documentation."
":kbd:`F2`", "Alternative to :kbd:`Ctrl-E`."
":kbd:`F3`", "Find next occurrence of word in current document."
":kbd:`F5`", "Export project dialog."
":kbd:`F7`", "Re-run spell checker."
":kbd:`F9`", "Re-build project indices."
":kbd:`Shift-F3`", "Find previous occurrence of word in current document."
":kbd:`Return`", "If in tree view, open a document for editing."
@@ -1,6 +1,6 @@
############
Introduction
============
############
novelWriter is a simple multi-document plain text editor using a modified
markdown syntax to apply simple formatting. Additional features are available
@@ -1,12 +1,14 @@
########################
Supporting Files (Notes)
========================
########################
Supporting files, or notes, are any files stored in root folders that are not the Novel root folder.
These files are intended for summaries and outlines of the various plot elements, characters, locations, and so on, of the novel.
These are not required, but making at least minimal files for each such element makes it possible to use the timeline view feature to see how each element intersects with each section of the novel itself.
*********
File Tags
^^^^^^^^^
*********
Each note file can have a tag associated with it,
The format of a tag is ``@tag: tagname``, where tagname is a unique identifier.
@@ -1,5 +1,6 @@
##############
Novel Projects
==============
##############
A novelWriter project requires a dedicated folder for storing its files.
See the Technical Information section for further details.
@@ -10,8 +11,9 @@ A list of recently opened projects is also maintained and can be selected from t
The project specific settings are available in :menuselection:`Project --> Project Settings`.
See further details below.
*****************
Project Structure
^^^^^^^^^^^^^^^^^
*****************
Projects are structured into a set of root folders, visible in the left side tree view panel.
@@ -36,7 +38,7 @@ Deleted files will be moved into a special "Trash" root folder.
Currently, these files cannot be permanently deleted from the project.
Orphaned Documents
~~~~~~~~~~~~~~~~~~
==================
In the event the editor crashes or otherwise exits without saving the project state, files that have been added to the project tree and are saved to disk will appear in a special "Orphaned Items" root folder next time the application is started.
These orphaned files will not have any meta data associated with them, so the title and other information has to be set again, and the files moved back to the correct location in the project.
@@ -44,14 +46,15 @@ Using Project Folders
Folders, aside from root folders, have no structural significance to the project. They are there purely as a way for the user to organise the files in meaningful sections and to be able to close them in the tree view. When processing the files in the novel, the folders are ignored.
****************
Project Settings
^^^^^^^^^^^^^^^^
****************
The project settings can be accessed from the :menuselection:`Project --> Project Settings` menu entry.
This will open a dialog box.
Settings Tab
~~~~~~~~~~~~
============
The Settings tab holds the project title and author settings.
Working Title can be set to a different title than the Book Title.
@@ -62,7 +65,7 @@ The Book Authors text box takes one author per line.
The line breaks matter in that this is converted to a list for later correct formatting.
Status Tab
~~~~~~~~~~
==========
Each file of type NOVEL can be given a status level, signified by a coloured icon.
These are purely there for the user's convenience, and you are not required to use them for any other feature to work.
@@ -71,7 +74,7 @@ The intention is to use this list to set what stage of writing you are on, altho
Note that status levels currently in use by a file cannot be deleted.
Importance Tab
~~~~~~~~~~~~~~
==============
Each file of types PLOT, CHARACTER, WORLD, TIMELINE, OBJECT or CUSTOM can be given an importance level, signified by a coloured icon like for status level.
These are also purely there for the user's convenience, and you are not required to use them for any other feature to work.
@@ -81,7 +84,7 @@ Again, these can in principle be used for whatever you want.
Note that importance levels currently in use by a file cannot be deleted.
Auto-Replace Tab
~~~~~~~~~~~~~~~~
================
A set of automatically replaced keywords can be added in this tab.
The keywords in the left column wile be replaced by the text in the right column when documents are opened in the viewer.
@@ -91,8 +94,9 @@ Note that a keyword cannot contain any spaces.
The angle brackets are added by default, and when used in the text are a part of the keyword to be replaced.
This is to ensure that parts of the text isn't unintentionally replaced by the content of the list.
*************
Writing Files
^^^^^^^^^^^^^
*************
New document files can be created from the Document menu, or by pressing Ctrl+N while in the tree view pane.
This will create a new, empty file, and open the item settings dialog where the filename and various other settings can be set.
@@ -100,8 +104,9 @@ This dialog can also be opened again later from either the menu, :menuselection:
The different classes of documents have some restrictions.
******
Backup
^^^^^^
******
An automatic backup system is built into novelWriter.
In order to use it, a backup path to where the backups are to be stored needs to be provided in :menuselection:`Tools --> Preferences`.
@@ -1,12 +1,13 @@
#################
Project Structure
=================
#################
This section concerns files under the Novel type root folder.
There are some restrictions and features that only applies to these type of files.
**********************
Importance of Headings
^^^^^^^^^^^^^^^^^^^^^^
**********************
Subfolders under root folders have no impact on the structure of the novel itself.
The structure is instead dictated by the heading level.
@@ -23,8 +24,9 @@ The different header levels are interpreted as specific section types of the nov
* ``### Header3``: Header level 3 signifies a scene level partition.
* ``#### Header4``: Header level 4 signifies a sub-scene level partition.
**************
Tag References
^^^^^^^^^^^^^^
**************
Each section, indicated by a heading, can contain references to tags set in the supporting files of the project.
See the File Tags section.
@@ -62,8 +64,9 @@ the syntax highlighter will alert the user that only the correct keywords are us
If the index of defined tags is out of date, press F9 to regenerate it, or select :menuselection:`Tools --> Rebuild Indices` from the menu.
In general, the index for a file is regenerated when a file is saved, so this shouldn't normally be necessary.
*****************
Novel File Layout
^^^^^^^^^^^^^^^^^
*****************
Files that exist under the NOVEL type root folder can have a number of layouts set.
See overview below.
@@ -1,14 +1,15 @@
#####################
Technical Information
=====================
#####################
This section contains details of how novelWriter stores and handles the project data.
******************
How Data is Stored
^^^^^^^^^^^^^^^^^^
******************
Main Project File
~~~~~~~~~~~~~~~~~
=================
The project itself requires a dedicated folder for storing its files.
The main project file is stored as an XML file with the name ``nwProject.nwx``.
@@ -21,7 +22,7 @@ It is important to keep this file backed up.
The project XML file is suitable for diff tools and version control, although a timesetamp is set in the meta section on line 2 each time the file is saved.
Project Documents
~~~~~~~~~~~~~~~~~
=================
The project documents are saved in folders staring with ``data_``.
Each document has a file handle taken from the first 13 characters of a SHA256 hash of the system time.
+2 -2
View File
@@ -87,8 +87,8 @@ class Config:
self.doReplaceDots = True
self.wordCountTimer = 5.0
self.fmtSingleQuotes = ["",""]
self.fmtDoubleQuotes = ["",""]
self.fmtSingleQuotes = ["\u2018","\u2019"]
self.fmtDoubleQuotes = ["\u201c","\u201d"]
self.spellLanguage = "en_GB"
+77
View File
@@ -0,0 +1,77 @@
# -*- coding: utf-8 -*-
"""novelWriter Concatenated File
novelWriter Concatenated File
=================================
Concatenate the standard novelWriter files to a single file
File History:
Created: 2019-10-26 [0.3.1]
"""
import logging
import nw
from os import path
from PyQt5.QtWidgets import QMessageBox
from nw.convert.file.text import TextFile
from nw.convert.tokenizer import Tokenizer
from nw.enum import nwAlert, nwItemLayout
logger = logging.getLogger(__name__)
class ConcatFile(TextFile):
def __init__(self, theProject, theParent):
TextFile.__init__(self, theProject, theParent)
self.theConv = Tokenizer(self.theProject, self.theParent)
return
def addText(self, tHandle):
logger.verbose("Parsing content of item '%s'" % tHandle)
theItem = self.theProject.getItem(tHandle)
isNone = theItem.itemLayout == nwItemLayout.NO_LAYOUT
isNote = theItem.itemLayout == nwItemLayout.NOTE
isNovel = not isNone and not isNote
if isNone:
return False
if isNote and not self.expNotes:
return False
if isNovel and not self.expNovel:
return False
self.theConv.setText(tHandle)
theResult = self.theConv.theText
if theResult is not None and self.outFile is not None:
self.outFile.write(theResult.rstrip())
self.outFile.write("\n\n")
return True
##
# Internal Functions
##
def _doOpenFile(self, filePath):
try:
self.outFile = open(filePath,mode="wt+")
except Exception as e:
self.makeAlert(["Failed to open file.",str(e)], nwAlert.ERROR)
return False
return True
def _doCloseFile(self):
if self.outFile is not None:
self.outFile.close()
return True
# END Class ConcatFile
@@ -13,9 +13,9 @@
import logging
import nw
from nw.convert.textfile import TextFile
from nw.convert.tohtml import ToHtml
from nw.enum import nwAlert
from nw.convert.file.text import TextFile
from nw.convert.text.tohtml import ToHtml
from nw.enum import nwAlert
logger = logging.getLogger(__name__)
@@ -34,15 +34,25 @@ class HtmlFile(TextFile):
def _doOpenFile(self, filePath):
try:
self.outFile = open(filePath,mode="w+")
self.outFile = open(filePath,mode="wt+")
self.outFile.write("<!DOCTYPE html>\n")
self.outFile.write("<html>\n")
self.outFile.write("<head>\n")
self.outFile.write("<style>\n")
self.outFile.write(" pre {background-color: #ffff99;}\n")
self.outFile.write(" #page {\n")
self.outFile.write(" margin: 40px auto;\n")
self.outFile.write(" max-width: 769px;\n")
self.outFile.write(" }\n")
self.outFile.write(" .comment {\n")
self.outFile.write(" background-color: #fbfabd;\n")
self.outFile.write(" border: 1px solid #b4b000;\n")
self.outFile.write(" margin: 10px 20px;\n")
self.outFile.write(" padding: 6px;\n")
self.outFile.write(" }\n")
self.outFile.write("</style>\n")
self.outFile.write("</head>\n")
self.outFile.write("<body>\n")
self.outFile.write("<article id='page'>\n")
except Exception as e:
self.makeAlert(["Failed to open file.",str(e)], nwAlert.ERROR)
return False
@@ -50,6 +60,7 @@ class HtmlFile(TextFile):
def _doCloseFile(self):
if self.outFile is not None:
self.outFile.write("</article>\n")
self.outFile.write("</body>\n")
self.outFile.write("</html>\n")
self.outFile.close()
+56
View File
@@ -0,0 +1,56 @@
# -*- coding: utf-8 -*-
"""novelWriter LaTeX File
novelWriter LaTeX File
==========================
Writes the project to a LaTeX file
File History:
Created: 2019-10-24 [0.3.1]
"""
import logging
import nw
from nw.convert.file.text import TextFile
from nw.convert.text.tolatex import ToLaTeX
from nw.enum import nwAlert
logger = logging.getLogger(__name__)
class LaTeXFile(TextFile):
def __init__(self, theProject, theParent):
TextFile.__init__(self, theProject, theParent)
self.theConv = ToLaTeX(self.theProject, self.theParent)
return
##
# Internal Functions
##
def _doOpenFile(self, filePath):
try:
self.outFile = open(filePath,mode="wt+")
self.outFile.write(r"\documentclass[12pt]{report}\n")
self.outFile.write(r"\usepackage[utf8]{inputenc}\n")
self.outFile.write(r"\begin{document}\n")
except Exception as e:
self.makeAlert(["Failed to open file.",str(e)], nwAlert.ERROR)
return False
return True
def _doCloseFile(self):
if self.outFile is not None:
self.outFile.write(r"\end{document}\n")
self.outFile.close()
return True
# END Class LaTeXFile
@@ -13,9 +13,9 @@
import logging
import nw
from nw.convert.textfile import TextFile
from nw.convert.tomarkdown import ToMarkdown
from nw.enum import nwAlert
from nw.convert.file.text import TextFile
from nw.convert.text.tomarkdown import ToMarkdown
from nw.enum import nwAlert
logger = logging.getLogger(__name__)
@@ -34,7 +34,7 @@ class MarkdownFile(TextFile):
def _doOpenFile(self, filePath):
try:
self.outFile = open(filePath,mode="w+")
self.outFile = open(filePath,mode="wt+")
except Exception as e:
self.makeAlert(["Failed to open file.",str(e)], nwAlert.ERROR)
return False
@@ -16,8 +16,8 @@ import nw
from os import path
from PyQt5.QtWidgets import QMessageBox
from nw.convert.tokenizer import Tokenizer
from nw.enum import nwAlert, nwItemLayout
from nw.convert.text.totext import ToText
from nw.enum import nwAlert, nwItemLayout
logger = logging.getLogger(__name__)
@@ -34,9 +34,8 @@ class TextFile():
self.theText = ""
self.expNovel = True
self.expNotes = False
self.winEnding = False
self.theConv = Tokenizer(self.theProject, self.theParent)
self.theConv = ToText(self.theProject, self.theParent)
self.makeAlert = self.theParent.makeAlert
self.setComments(False)
@@ -140,9 +139,6 @@ class TextFile():
self.theConv.doHeaders()
self.theConv.doConvert()
if self.winEnding:
self.theConv.windowsEndings()
if self.theConv.theResult is not None and self.outFile is not None:
self.outFile.write(self.theConv.theResult)
@@ -157,11 +153,8 @@ class TextFile():
that uses a different file format that requires a different approach.
"""
try:
self.outFile = open(filePath,mode="w+")
if self.winEnding:
self.outFile.write("\r\n\r\n")
else:
self.outFile.write("\n\n")
self.outFile = open(filePath,mode="wt+")
self.outFile.write("\n\n")
except Exception as e:
self.makeAlert(["Failed to open file.",str(e)], nwAlert.ERROR)
return False
@@ -28,9 +28,13 @@ class ToHtml(Tokenizer):
Tokenizer.doAutoReplace(self)
repDict = {
"<" : "&lt;",
">" : "&gt;",
"&" : "&amp;",
"<" : "&lt;",
">" : "&gt;",
"&" : "&amp;",
"\u2013" : "&endash;",
"\u2014" : "$emdash;",
"\u2500" : "$emdash;",
"\u2026" : "&hellip;",
}
xRep = re.compile("|".join([re.escape(k) for k in repDict.keys()]), flags=re.DOTALL)
self.theText = xRep.sub(lambda x: repDict[x.group(0)], self.theText)
@@ -88,7 +92,7 @@ class ToHtml(Tokenizer):
thisPar.append(tTemp)
elif tType == self.T_COMMENT and self.doComments:
self.theResult += "<pre>%s</pre>\n" % tText
self.theResult += "<div class='comment'>%s</div>\n" % tText
elif tType == self.T_COMMAND and self.doCommands:
self.theResult += "<pre>%s</pre>\n" % tText
+155
View File
@@ -0,0 +1,155 @@
# -*- coding: utf-8 -*-
"""novelWriter LaTeX Converter
novelWriter LaTeX Converter
===============================
Extends the Tokenizer class to write LaTeX
File History:
Created: 2019-10-24 [0.3.1]
"""
import textwrap
import logging
import re
import nw
from nw.convert.tokenizer import Tokenizer
logger = logging.getLogger(__name__)
class ToLaTeX(Tokenizer):
def __init__(self, theProject, theParent):
Tokenizer.__init__(self, theProject, theParent)
return
def doAutoReplace(self):
Tokenizer.doAutoReplace(self)
repDict = {
"\u2013" : "--",
"\u2014" : "---",
"\u2500" : "---",
"\u2026" : "...",
}
xRep = re.compile("|".join([re.escape(k) for k in repDict.keys()]), flags=re.DOTALL)
self.theText = xRep.sub(lambda x: repDict[x.group(0)], self.theText)
return
def doConvert(self):
texTags = {
self.FMT_B_B : r"\textbf{",
self.FMT_B_E : r"}",
self.FMT_I_B : r"\textit{",
self.FMT_I_E : r"}",
self.FMT_U_B : r"\underline{",
self.FMT_U_E : r"}",
}
if self.wordWrap > 0:
tWrap = textwrap.TextWrapper(
width = self.wordWrap,
initial_indent = "",
subsequent_indent = "",
expand_tabs = True,
replace_whitespace = True,
fix_sentence_endings = False,
break_long_words = True,
drop_whitespace = True,
break_on_hyphens = True,
tabsize = 8,
max_lines = None
)
tComm = textwrap.TextWrapper(
width = self.wordWrap-2,
initial_indent = "",
subsequent_indent = "",
expand_tabs = True,
replace_whitespace = True,
fix_sentence_endings = False,
break_long_words = True,
drop_whitespace = True,
break_on_hyphens = True,
tabsize = 8,
max_lines = None
)
self.theResult = ""
thisPar = []
for tType, tText, tFormat, tAlign in self.theTokens:
begText = ""
endText = "\n"
if tAlign == self.A_CENTRE:
begText = "\\begin{center}\n"
endText = "\\end{center}\n\n"
# First check if we have a comment or plain text, as they need some
# extra replacing before we proceed to wrapping and final formatting.
if tType == self.T_COMMENT:
tText = "%% %s" % tText
elif tType == self.T_TEXT:
tTemp = tText
for xPos, xLen, xFmt in reversed(tFormat):
tTemp = tTemp[:xPos]+texTags[xFmt]+tTemp[xPos+xLen:]
tText = tTemp
tLen = len(tText)
# The text can now be word wrapped, if we have requested this and it's needed.
if self.wordWrap > 0 and tLen > self.wordWrap:
if tType == self.T_COMMENT:
aText = tComm.wrap(tText)
tText = "\n% ".join(aText)
else:
tText = tWrap.fill(tText)
# Then the text can receive final formatting before we append it to the results.
# We also store text lines in a buffer and merge them only when we find an empty line,
# indicating a new paragraph.
if tType == self.T_EMPTY:
if len(thisPar) > 0:
self.theResult += begText
self.theResult += "%s\n" % tWrap.fill(" ".join(thisPar))
self.theResult += endText
thisPar = []
elif tType == self.T_HEAD1:
self.theResult += begText
self.theResult += "{\\Huge %s}\n" % tText
self.theResult += endText
elif tType == self.T_HEAD2:
self.theResult += "\\chapter*{%s}\n\n" % tText
elif tType == self.T_HEAD3:
self.theResult += "\\section*{%s}\n\n" % tText
elif tType == self.T_HEAD4:
self.theResult += "\\subsection*{%s}\n\n" % tText
elif tType == self.T_SEP:
self.theResult += begText
self.theResult += "%s\n" % tText
self.theResult += endText
elif tType == self.T_TEXT:
thisPar.append(tText)
elif tType == self.T_PBREAK:
self.theResult += "\\newpage\n\n"
elif tType == self.T_COMMENT and self.doComments:
self.theResult += "%s\n\n" % tText
elif tType == self.T_COMMAND and self.doCommands:
self.theResult += "%% @%s\n\n" % tText
return
# END Class ToLaTeX
+119
View File
@@ -0,0 +1,119 @@
# -*- coding: utf-8 -*-
"""novelWriter Plain Text Converter
novelWriter Plain Text Converter
====================================
Extends the Tokenizer class to convert to plain text
File History:
Created: 2019-10-26 [0.3.1]
"""
import textwrap
import logging
import re
import nw
from nw.convert.tokenizer import Tokenizer
logger = logging.getLogger(__name__)
class ToText(Tokenizer):
def __init__(self, theProject, theParent):
Tokenizer.__init__(self, theProject, theParent)
return
def doConvert(self):
"""Converts the tokenized text into plain text.
"""
if self.wordWrap > 0:
tWrap = textwrap.TextWrapper(
width = self.wordWrap,
initial_indent = "",
subsequent_indent = "",
expand_tabs = True,
replace_whitespace = True,
fix_sentence_endings = False,
break_long_words = True,
drop_whitespace = True,
break_on_hyphens = True,
tabsize = 8,
max_lines = None
)
self.theResult = ""
thisPar = []
for tType, tText, tFormat, tAlign in self.theTokens:
# First check if we have a comment or plain text, as they need some
# extra replacing before we proceed to wrapping and final formatting.
if tType == self.T_COMMENT:
tText = "[%s]" % tText
elif tType == self.T_TEXT:
tTemp = tText
for xPos, xLen, xFmt in reversed(tFormat):
tTemp = tTemp[:xPos]+tTemp[xPos+xLen:]
tText = tTemp
tLen = len(tText)
# The text can now be word wrapped, if we have requested this and it's needed.
if tAlign == self.A_CENTRE:
if self.wordWrap > 0:
if tLen > self.wordWrap:
aText = tWrap.wrap(tText)
for n in range(len(aText)):
aText[n] = self._centreText(aText[n],self.wordWrap)
tText = "\n".join(aText)
else:
tText = self._centreText(tText,self.wordWrap)
else:
if self.wordWrap > 0 and tLen > self.wordWrap:
tText = tWrap.fill(tText)
# Then the text can receive final formatting before we append it to the results.
# We also store text lines in a buffer and merge them only when we find an empty line,
# indicating a new paragraph.
if tType == self.T_EMPTY:
if len(thisPar) > 0:
self.theResult += "%s\n\n" % " ".join(thisPar)
thisPar = []
elif tType == self.T_HEAD1:
uLine = "="*min(tLen,self.wordWrap)
if tAlign == self.A_CENTRE:
uLine = self._centreText(uLine,self.wordWrap)
self.theResult += "%s\n%s\n\n" % (tText,uLine)
elif tType == self.T_HEAD2:
uLine = "~"*min(tLen,self.wordWrap)
self.theResult += "%s\n%s\n\n" % (tText,uLine)
elif tType == self.T_HEAD3:
uLine = "-"*min(tLen,self.wordWrap)
self.theResult += "%s\n%s\n\n" % (tText,uLine)
elif tType == self.T_HEAD4:
self.theResult += "%s\n\n" % tText
elif tType == self.T_SEP:
if self.wordWrap > 0 and tLen < self.wordWrap:
tText = self._centreText(tText,self.wordWrap)
self.theResult += "%s\n\n" % tText
elif tType == self.T_TEXT:
thisPar.append(tText)
elif tType == self.T_COMMENT and self.doComments:
self.theResult += "%s\n\n" % tText
elif tType == self.T_COMMAND and self.doCommands:
self.theResult += "%s\n\n" % tText
return
# END Class ToText
+22 -113
View File
@@ -26,27 +26,28 @@ logger = logging.getLogger(__name__)
class Tokenizer():
FMT_B_B = 1 # Begin bold
FMT_B_E = 2 # End bold
FMT_I_B = 3 # Begin italics
FMT_I_E = 4 # End italics
FMT_U_B = 5 # Begin underline
FMT_U_E = 6 # End underline
FMT_B_B = 1 # Begin bold
FMT_B_E = 2 # End bold
FMT_I_B = 3 # Begin italics
FMT_I_E = 4 # End italics
FMT_U_B = 5 # Begin underline
FMT_U_E = 6 # End underline
T_EMPTY = 1 # Empty line (new paragraph)
T_COMMENT = 2 # Comment line
T_COMMAND = 3 # Command line
T_HEAD1 = 4 # Header 1 (title)
T_HEAD2 = 5 # Header 2 (chapter)
T_HEAD3 = 6 # Header 3 (scene)
T_HEAD4 = 7 # Header 4
T_TEXT = 8 # Text line
T_SEP = 9 # Scene separator
T_EMPTY = 1 # Empty line (new paragraph)
T_COMMENT = 2 # Comment line
T_COMMAND = 3 # Command line
T_HEAD1 = 4 # Header 1 (title)
T_HEAD2 = 5 # Header 2 (chapter)
T_HEAD3 = 6 # Header 3 (scene)
T_HEAD4 = 7 # Header 4
T_TEXT = 8 # Text line
T_SEP = 9 # Scene separator
T_PBREAK = 10 # Page break
A_LEFT = 1 # Left aligned
A_RIGHT = 2 # Right aligned
A_CENTRE = 3 # Centred
A_JUSTIFY = 4 # Justified
A_LEFT = 1 # Left aligned
A_RIGHT = 2 # Right aligned
A_CENTRE = 3 # Centred
A_JUSTIFY = 4 # Justified
def __init__(self, theProject, theParent):
@@ -262,6 +263,7 @@ class Tokenizer():
self.theTokens[n] = (tType,tTemp,None,self.A_LEFT)
# For title page and partitions, we need to centre all text
# and for some formats, we need a page break
if isTitle or isPart:
for n in range(len(self.theTokens)):
tToken = self.theTokens[n]
@@ -270,101 +272,8 @@ class Tokenizer():
tFormat = tToken[2]
self.theTokens[n] = (tType,tText,tFormat,self.A_CENTRE)
return
self.theTokens[n] = (self.T_PBREAK,"",None,self.A_LEFT)
def doConvert(self):
"""Converts the tokenized text into plain text.
"""
if self.wordWrap > 0:
tWrap = textwrap.TextWrapper(
width = self.wordWrap,
initial_indent = "",
subsequent_indent = "",
expand_tabs = True,
replace_whitespace = True,
fix_sentence_endings = False,
break_long_words = True,
drop_whitespace = True,
break_on_hyphens = True,
tabsize = 8,
max_lines = None
)
self.theResult = ""
thisPar = []
for tType, tText, tFormat, tAlign in self.theTokens:
# First check if we have a comment or plain text, as they need some
# extra replacing before we proceed to wrapping and final formatting.
if tType == self.T_COMMENT:
tText = "[%s]" % tText
elif tType == self.T_TEXT:
tTemp = tText
for xPos, xLen, xFmt in reversed(tFormat):
tTemp = tTemp[:xPos]+tTemp[xPos+xLen:]
tText = tTemp
tLen = len(tText)
# The text can now be word wrapped, if we have requested this and it's needed.
if tAlign == self.A_CENTRE:
if self.wordWrap > 0:
if tLen > self.wordWrap:
aText = tWrap.wrap(tText)
for n in range(len(aText)):
aText[n] = self._centreText(aText[n],self.wordWrap)
tText = "\n".join(aText)
else:
tText = self._centreText(tText,self.wordWrap)
else:
if self.wordWrap > 0 and tLen > self.wordWrap:
tText = tWrap.fill(tText)
# Then the text can receive final formatting before we append it to the results.
# We also store text lines in a buffer and merge them only when we find an empty line,
# indicating a new paragraph.
if tType == self.T_EMPTY:
if len(thisPar) > 0:
self.theResult += "%s\n\n" % " ".join(thisPar)
thisPar = []
elif tType == self.T_HEAD1:
uLine = "="*min(tLen,self.wordWrap)
if tAlign == self.A_CENTRE:
uLine = self._centreText(uLine,self.wordWrap)
self.theResult += "%s\n%s\n\n" % (tText,uLine)
elif tType == self.T_HEAD2:
uLine = "~"*min(tLen,self.wordWrap)
self.theResult += "%s\n%s\n\n" % (tText,uLine)
elif tType == self.T_HEAD3:
uLine = "-"*min(tLen,self.wordWrap)
self.theResult += "%s\n%s\n\n" % (tText,uLine)
elif tType == self.T_HEAD4:
self.theResult += "%s\n\n" % tText
elif tType == self.T_SEP:
if self.wordWrap > 0 and tLen < self.wordWrap:
tText = self._centreText(tText,self.wordWrap)
self.theResult += "%s\n\n" % tText
elif tType == self.T_TEXT:
thisPar.append(tText)
elif tType == self.T_COMMENT and self.doComments:
self.theResult += "%s\n\n" % tText
elif tType == self.T_COMMAND and self.doCommands:
self.theResult += "%s\n\n" % tText
return
def windowsEndings(self):
self.theResult = self.theResult.replace("\n","\r\n")
return
##
+4 -4
View File
@@ -422,15 +422,15 @@ class GuiDocEditor(QTextEdit):
elif self.mainConf.doReplaceDash and theTwo == "--":
theCursor.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor, 2)
theCursor.insertText("")
theCursor.insertText("\u2013")
elif self.mainConf.doReplaceDash and theTwo == "-":
elif self.mainConf.doReplaceDash and theTwo == "\u2013-":
theCursor.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor, 2)
theCursor.insertText("")
theCursor.insertText("\u2014")
elif self.mainConf.doReplaceDots and theThree == "...":
theCursor.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor, 3)
theCursor.insertText("")
theCursor.insertText("\u2026")
return
+3 -3
View File
@@ -17,9 +17,9 @@ from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QTextBrowser
from PyQt5.QtGui import QTextOption, QFont, QPalette, QColor
from nw.convert.tokenizer import Tokenizer
from nw.convert.tohtml import ToHtml
from nw.enum import nwItemType
from nw.convert.tokenizer import Tokenizer
from nw.convert.text.tohtml import ToHtml
from nw.enum import nwItemType
logger = logging.getLogger(__name__)
+39 -24
View File
@@ -23,14 +23,16 @@ from PyQt5.QtWidgets import (
QLabel, QComboBox, QLineEdit, QPushButton, QFileDialog, QProgressBar, QSpinBox
)
from nw.project.document import NWDoc
from nw.tools.translate import numberToWord
from nw.tools.optlaststate import OptLastState
from nw.convert.textfile import TextFile
from nw.convert.htmlfile import HtmlFile
from nw.convert.markdownfile import MarkdownFile
from nw.constants import nwFiles
from nw.enum import nwItemType
from nw.project.document import NWDoc
from nw.tools.translate import numberToWord
from nw.tools.optlaststate import OptLastState
from nw.convert.file.text import TextFile
from nw.convert.file.html import HtmlFile
from nw.convert.file.markdown import MarkdownFile
from nw.convert.file.latex import LaTeXFile
from nw.convert.file.concat import ConcatFile
from nw.constants import nwFiles
from nw.enum import nwItemType
logger = logging.getLogger(__name__)
@@ -127,6 +129,10 @@ class GuiExport(QDialog):
outFile = MarkdownFile(self.theProject, self.theParent)
elif eFormat == GuiExportMain.FMT_HTML:
outFile = HtmlFile(self.theProject, self.theParent)
elif eFormat == GuiExportMain.FMT_TEX:
outFile = LaTeXFile(self.theProject, self.theParent)
elif eFormat == GuiExportMain.FMT_NWD:
outFile = ConcatFile(self.theProject, self.theParent)
if outFile is None:
return False
@@ -203,13 +209,15 @@ class GuiExport(QDialog):
class GuiExportMain(QWidget):
FMT_TXT = 1
FMT_MD = 2
FMT_HTML = 3
FMT_EBOOK = 4
FMT_ODT = 5
FMT_TEX = 6
FMT_NWD = 1 # novelWriter markdown
FMT_TXT = 2 # Plain text file
FMT_MD = 3 # Markdown file
FMT_HTML = 4 # HTML file
FMT_EBOOK = 5 # E-book friendly HTML
FMT_ODT = 6 # Open document
FMT_TEX = 7 # LaTeX file
FMT_EXT = {
FMT_NWD : ".nwd",
FMT_TXT : ".txt",
FMT_MD : ".md",
FMT_HTML : ".htm",
@@ -218,6 +226,10 @@ class GuiExportMain(QWidget):
FMT_TEX : ".tex",
}
FMT_HELP = {
FMT_NWD : (
"Exports a document using the novelWriter markdown format. "
"The files selected by the filters are appended as-is, including comments and other settings."
),
FMT_TXT : (
"Exports a plain text file. "
"All formatting is stripped and comments are in square brackets."
@@ -228,7 +240,7 @@ class GuiExportMain(QWidget):
),
FMT_HTML : (
"Exports a plain html5 file. "
"Comments are converted to preformatted text blocks."
"Comments are wrapped in blocks with a yellow background colour."
),
FMT_EBOOK : (
"Exports an html5 file that can be converted to eBook with Calibre. "
@@ -343,12 +355,13 @@ class GuiExportMain(QWidget):
self.outputHelp.setAlignment(Qt.AlignTop)
self.outputFormat = QComboBox(self)
self.outputFormat.addItem("Plain Text (.txt)", self.FMT_TXT)
self.outputFormat.addItem("Markdown (.md)", self.FMT_MD)
self.outputFormat.addItem("HTML5 (.htm)", self.FMT_HTML)
# self.outputFormat.addItem("HTML5 for eBook (.htm)", self.FMT_EBOOK)
# self.outputFormat.addItem("Open Document (.odt)", self.FMT_ODT)
# self.outputFormat.addItem("LaTeX for PDF (.tex)", self.FMT_TEX)
self.outputFormat.addItem("novelWriter Markdown (.nwd)", self.FMT_NWD)
self.outputFormat.addItem("Plain Text (.txt)", self.FMT_TXT)
self.outputFormat.addItem("Markdown (.md)", self.FMT_MD)
self.outputFormat.addItem("HTML5 (.htm)", self.FMT_HTML)
# self.outputFormat.addItem("HTML5 for eBook (.htm)", self.FMT_EBOOK)
# self.outputFormat.addItem("Open Document (.odt)", self.FMT_ODT)
self.outputFormat.addItem("LaTeX for PDF (.tex)", self.FMT_TEX)
self.outputFormat.currentIndexChanged.connect(self._updateFormat)
optIdx = self.outputFormat.findData(self.optState.getSetting("eFormat"))
@@ -371,7 +384,7 @@ class GuiExportMain(QWidget):
self.fixedWidth.setMaximum(999)
self.fixedWidth.setSingleStep(1)
self.fixedWidth.setValue(self.optState.getSetting("fixWidth"))
self.fixedWidth.setToolTip("0 disables the feature. Applies to .txt, .md and .tex files.")
self.fixedWidth.setToolTip("Applies to .txt, .md and .tex files. 0 disables the feature.")
self.addSettingsForm.addWidget(QLabel("Fixed width"), 0, 0)
self.addSettingsForm.addWidget(self.fixedWidth, 0, 1)
@@ -413,11 +426,13 @@ class GuiExportMain(QWidget):
currDir = ""
extFilter = [
"novelWriter document files (*.nwd)",
"Text files (*.txt)",
"Markdown files (*.md)",
"HTML files (*.htm *.html)",
"Open document files (*.odt)",
# "Open document files (*.odt)",
"LaTeX files (*.tex)",
"All files (*.*)",
]
dlgOpt = QFileDialog.Options()
@@ -450,7 +465,7 @@ class ExportLastState(OptLastState):
self.theState = {
"wNovel" : True,
"wNotes" : False,
"eFormat" : 2,
"eFormat" : 1,
"fixWidth" : 80,
"wComments" : False,
"chFormat" : "Chapter %numword%",