Add Url recognition and formatting (#2067)

This commit is contained in:
Veronica Berglyd Olsen
2024-10-27 00:17:11 +02:00
committed by GitHub
48 changed files with 818 additions and 287 deletions
+1
View File
@@ -60,6 +60,7 @@ class nwConst:
class nwRegEx: class nwRegEx:
URL = r"https?://(?:www\.|(?!www))[\w/()@:%_\+-.~#?&=]+"
WORDS = r"\b[^\s\-\+\/–—\[\]:]+\b" WORDS = r"\b[^\s\-\+\/–—\[\]:]+\b"
BREAK = r"(?i)(?<!\\)(\[br\]\n?)" BREAK = r"(?i)(?<!\\)(\[br\]\n?)"
FMT_EI = r"(?<![\w\\])(_)(?![\s_])(.+?)(?<![\s\\])(\1)(?!\w)" FMT_EI = r"(?<![\w\\])(_)(?![\s_])(.+?)(?<![\s\\])(\1)(?!\w)"
+5 -5
View File
@@ -52,7 +52,7 @@ class NPagedSideBar(QToolBar):
self._labelCol = None self._labelCol = None
self._spacerHeight = self.fontMetrics().height() // 2 self._spacerHeight = self.fontMetrics().height() // 2
self._buttons: dict[int, _NPagedToolButton] = {} self._buttons: dict[int, _PagedToolButton] = {}
self._group = QButtonGroup(self) self._group = QButtonGroup(self)
self._group.setExclusive(True) self._group.setExclusive(True)
@@ -67,7 +67,7 @@ class NPagedSideBar(QToolBar):
return return
def button(self, buttonId: int) -> _NPagedToolButton: def button(self, buttonId: int) -> _PagedToolButton:
"""Return a specific button.""" """Return a specific button."""
return self._buttons[buttonId] return self._buttons[buttonId]
@@ -85,7 +85,7 @@ class NPagedSideBar(QToolBar):
def addButton(self, text: str, buttonId: int = -1) -> QAction: def addButton(self, text: str, buttonId: int = -1) -> QAction:
"""Add a new button to the toolbar.""" """Add a new button to the toolbar."""
button = _NPagedToolButton(self) button = _PagedToolButton(self)
button.setText(text) button.setText(text)
action = self.insertWidget(self._stretchAction, button) action = self.insertWidget(self._stretchAction, button)
@@ -113,7 +113,7 @@ class NPagedSideBar(QToolBar):
return return
class _NPagedToolButton(QToolButton): class _PagedToolButton(QToolButton):
__slots__ = ("_bH", "_tM", "_lM", "_cR", "_aH") __slots__ = ("_bH", "_tM", "_lM", "_cR", "_aH")
@@ -154,7 +154,7 @@ class _NPagedToolButton(QToolButton):
height = self.height() height = self.height()
palette = self.palette() palette = self.palette()
if opt.state & QtMouseOver == QtMouseOver: if opt.state & QtMouseOver == QtMouseOver: # pragma: no cover
backCol = palette.base() backCol = palette.base()
paint.setBrush(backCol) paint.setBrush(backCol)
paint.setOpacity(0.75) paint.setOpacity(0.75)
+7 -4
View File
@@ -46,6 +46,7 @@ class TextDocumentTheme:
text: QColor = QColor(0, 0, 0) text: QColor = QColor(0, 0, 0)
highlight: QColor = QColor(255, 255, 166) highlight: QColor = QColor(255, 255, 166)
head: QColor = QColor(66, 113, 174) head: QColor = QColor(66, 113, 174)
link: QColor = QColor(66, 113, 174)
comment: QColor = QColor(100, 100, 100) comment: QColor = QColor(100, 100, 100)
note: QColor = QColor(129, 55, 9) note: QColor = QColor(129, 55, 9)
code: QColor = QColor(66, 113, 174) code: QColor = QColor(66, 113, 174)
@@ -86,10 +87,12 @@ class TextFmt(IntEnum):
COL_E = 16 # End colour COL_E = 16 # End colour
ANM_B = 17 # Begin anchor name ANM_B = 17 # Begin anchor name
ANM_E = 18 # End anchor name ANM_E = 18 # End anchor name
HRF_B = 19 # Begin href link ARF_B = 19 # Begin anchor link
HRF_E = 20 # End href link ARF_E = 20 # End anchor link
FNOTE = 21 # Footnote marker HRF_B = 21 # Begin href link
STRIP = 22 # Strip the format code HRF_E = 22 # End href link
FNOTE = 23 # Footnote marker
STRIP = 24 # Strip the format code
class BlockTyp(IntEnum): class BlockTyp(IntEnum):
+217 -103
View File
@@ -87,6 +87,17 @@ def _docXCol(color: QColor) -> str:
return color.name(QtHexRgb).lstrip("#") return color.name(QtHexRgb).lstrip("#")
def _wText(parent: ET.Element, text: str) -> ET.Element:
"""Create a text element and add the preserve flag if necessary."""
attrib = {}
if len(text) > len(text.strip()):
attrib[_mkTag("xml", "space")] = "preserve"
return xmlSubElem(parent, _wTag("t"), text, attrib=attrib)
# Cached
W_VAL = _wTag("val")
# Formatting Codes # Formatting Codes
X_BLD = 0x001 # Bold format X_BLD = 0x001 # Bold format
X_ITA = 0x002 # Italic format X_ITA = 0x002 # Italic format
@@ -96,6 +107,7 @@ X_MRK = 0x010 # Marked format
X_SUP = 0x020 # Superscript X_SUP = 0x020 # Superscript
X_SUB = 0x040 # Subscript X_SUB = 0x040 # Subscript
X_COL = 0x080 # Coloured text X_COL = 0x080 # Coloured text
X_HRF = 0x100 # Link
# Formatting Masks # Formatting Masks
M_BLD = ~X_BLD M_BLD = ~X_BLD
@@ -106,6 +118,7 @@ M_MRK = ~X_MRK
M_SUP = ~X_SUP M_SUP = ~X_SUP
M_SUB = ~X_SUB M_SUB = ~X_SUB
M_COL = ~X_COL M_COL = ~X_COL
M_HRF = ~X_HRF
# DocX Styles # DocX Styles
S_NORM = "Normal" S_NORM = "Normal"
@@ -120,12 +133,17 @@ S_HEAD = "Header"
S_FNOTE = "FootnoteText" S_FNOTE = "FootnoteText"
class DocXXmlRel(NamedTuple):
rId: str
relType: str
targetMode: str | None = None
class DocXXmlFile(NamedTuple): class DocXXmlFile(NamedTuple):
xml: ET.Element xml: ET.Element
rId: str
path: str path: str
relType: str
contentType: str contentType: str
@@ -138,9 +156,9 @@ class DocXParStyle(NamedTuple):
nextStyle: str | None = None nextStyle: str | None = None
before: float | None = None before: float | None = None
after: float | None = None after: float | None = None
left: float | None = None
line: float | None = None line: float | None = None
indentFirst: float | None = None indentFirst: float | None = None
hanging: float | None = None
align: str | None = None align: str | None = None
default: bool = False default: bool = False
level: int | None = None level: int | None = None
@@ -170,6 +188,7 @@ class ToDocX(Tokenizer):
# Data Variables # Data Variables
self._pars: list[DocXParagraph] = [] self._pars: list[DocXParagraph] = []
self._rels: dict[str, DocXXmlRel] = {}
self._files: dict[str, DocXXmlFile] = {} self._files: dict[str, DocXXmlFile] = {}
self._styles: dict[str, DocXParStyle] = {} self._styles: dict[str, DocXParStyle] = {}
self._usedNotes: dict[str, int] = {} self._usedNotes: dict[str, int] = {}
@@ -183,7 +202,7 @@ class ToDocX(Tokenizer):
def setLanguage(self, language: str | None) -> None: def setLanguage(self, language: str | None) -> None:
"""Set language for the document.""" """Set language for the document."""
if language: if language:
self._dLanguage = language self._dLanguage = language.replace("_", "-")
return return
def setPageLayout( def setPageLayout(
@@ -288,6 +307,7 @@ class ToDocX(Tokenizer):
self._coreXml() self._coreXml()
self._appXml() self._appXml()
self._stylesXml() self._stylesXml()
self._fontTableXml()
fId = None fId = None
dId = None dId = None
@@ -320,13 +340,17 @@ class ToDocX(Tokenizer):
wRels = ET.Element("Relationships", attrib={ wRels = ET.Element("Relationships", attrib={
"xmlns": f"{OOXML_SCM}/package/2006/relationships" "xmlns": f"{OOXML_SCM}/package/2006/relationships"
}) })
for name, entry in self._files.items(): for name, rel in self._rels.items():
cDocs.append((f"/{entry.path}/{name}", entry.contentType))
isRoot = name in ("core.xml", "app.xml", "document.xml") isRoot = name in ("core.xml", "app.xml", "document.xml")
xmlSubElem(rRels if isRoot else wRels, "Relationship", attrib={ if xml := self._files.get(name):
"Id": entry.rId, "Type": entry.relType, target = f"{xml.path}/{name}" if isRoot else name
"Target": f"{entry.path}/{name}" if isRoot else name, cDocs.append((f"/{xml.path}/{name}", xml.contentType))
}) else:
target = name
attrib = {"Id": rel.rId, "Type": rel.relType, "Target": target}
if rel.targetMode:
attrib["TargetMode"] = rel.targetMode
xmlSubElem(rRels if isRoot else wRels, "Relationship", attrib=attrib)
# Content Types XML # Content Types XML
dTypes = ET.Element("Types", attrib={ dTypes = ET.Element("Types", attrib={
@@ -345,8 +369,8 @@ class ToDocX(Tokenizer):
with ZipFile(path, mode="w", compression=ZIP_DEFLATED, compresslevel=3) as outZip: with ZipFile(path, mode="w", compression=ZIP_DEFLATED, compresslevel=3) as outZip:
xmlToZip("_rels/.rels", rRels, outZip) xmlToZip("_rels/.rels", rRels, outZip)
xmlToZip("word/_rels/document.xml.rels", wRels, outZip) xmlToZip("word/_rels/document.xml.rels", wRels, outZip)
for name, entry in self._files.items(): for name, rel in self._files.items():
xmlToZip(f"{entry.path}/{name}", entry.xml, outZip) xmlToZip(f"{rel.path}/{name}", rel.xml, outZip)
xmlToZip("[Content_Types].xml", dTypes, outZip) xmlToZip("[Content_Types].xml", dTypes, outZip)
return return
@@ -363,6 +387,7 @@ class ToDocX(Tokenizer):
xFmt = 0x00 xFmt = 0x00
xNode = None xNode = None
fStart = 0 fStart = 0
fLink = ""
fClass = "" fClass = ""
for fPos, fFmt, fData in tFmt or []: for fPos, fFmt, fData in tFmt or []:
@@ -371,7 +396,7 @@ class ToDocX(Tokenizer):
xNode = None xNode = None
if temp := text[fStart:fPos]: if temp := text[fStart:fPos]:
par.addContent(self._textRunToXml(temp, xFmt, fClass)) par.addContent(self._textRunToXml(temp, xFmt, fClass, fLink))
if fFmt == TextFmt.B_B: if fFmt == TextFmt.B_B:
xFmt |= X_BLD xFmt |= X_BLD
@@ -407,6 +432,12 @@ class ToDocX(Tokenizer):
elif fFmt == TextFmt.COL_E: elif fFmt == TextFmt.COL_E:
xFmt &= M_COL xFmt &= M_COL
fClass = "" fClass = ""
elif fFmt == TextFmt.HRF_B:
xFmt |= X_HRF
fLink = fData
elif fFmt == TextFmt.HRF_E:
xFmt &= M_HRF
fLink = ""
elif fFmt == TextFmt.FNOTE: elif fFmt == TextFmt.FNOTE:
xNode = self._generateFootnote(fData) xNode = self._generateFootnote(fData)
elif fFmt == TextFmt.STRIP: elif fFmt == TextFmt.STRIP:
@@ -419,44 +450,49 @@ class ToDocX(Tokenizer):
par.addContent(xNode) par.addContent(xNode)
if temp := text[fStart:]: if temp := text[fStart:]:
par.addContent(self._textRunToXml(temp, xFmt, fClass)) par.addContent(self._textRunToXml(temp, xFmt, fClass, fLink))
return return
def _textRunToXml(self, text: str, fmt: int, fClass: str = "") -> ET.Element: def _textRunToXml(self, text: str, fmt: int, fClass: str, fLink: str) -> ET.Element:
"""Encode the text run into XML.""" """Encode the text run into XML."""
run = ET.Element(_wTag("r")) xR = ET.Element(_wTag("r"))
rPr = xmlSubElem(run, _wTag("rPr")) rPr = xmlSubElem(xR, _wTag("rPr"))
if fmt & X_BLD: if fmt & X_BLD:
xmlSubElem(rPr, _wTag("b")) xmlSubElem(rPr, _wTag("b"))
if fmt & X_ITA: if fmt & X_ITA:
xmlSubElem(rPr, _wTag("i")) xmlSubElem(rPr, _wTag("i"))
if fmt & X_UND: if fmt & X_UND:
xmlSubElem(rPr, _wTag("u"), attrib={_wTag("val"): "single"}) xmlSubElem(rPr, _wTag("u"), attrib={W_VAL: "single"})
if fmt & X_MRK: if fmt & X_MRK:
xmlSubElem(rPr, _wTag("shd"), attrib={ xmlSubElem(rPr, _wTag("shd"), attrib={
_wTag("fill"): _docXCol(self._theme.highlight), _wTag("val"): "clear", _wTag("fill"): _docXCol(self._theme.highlight), W_VAL: "clear",
}) })
if fmt & X_DEL: if fmt & X_DEL:
xmlSubElem(rPr, _wTag("strike")) xmlSubElem(rPr, _wTag("strike"))
if fmt & X_SUP: if fmt & X_SUP:
xmlSubElem(rPr, _wTag("vertAlign"), attrib={_wTag("val"): "superscript"}) xmlSubElem(rPr, _wTag("vertAlign"), attrib={W_VAL: "superscript"})
if fmt & X_SUB: if fmt & X_SUB:
xmlSubElem(rPr, _wTag("vertAlign"), attrib={_wTag("val"): "subscript"}) xmlSubElem(rPr, _wTag("vertAlign"), attrib={W_VAL: "subscript"})
if fmt & X_COL and (color := self._classes.get(fClass)): if fmt & X_COL and (color := self._classes.get(fClass)):
xmlSubElem(rPr, _wTag("color"), attrib={_wTag("val"): _docXCol(color)}) xmlSubElem(rPr, _wTag("color"), attrib={W_VAL: _docXCol(color)})
for segment in RX_TEXT.split(text): for segment in RX_TEXT.split(text):
if segment == "\n": if segment == "\n":
xmlSubElem(run, _wTag("br")) xmlSubElem(xR, _wTag("br"))
elif segment == "\t": elif segment == "\t":
xmlSubElem(run, _wTag("tab")) xmlSubElem(xR, _wTag("tab"))
elif len(segment) != len(segment.strip()):
xmlSubElem(run, _wTag("t"), segment, attrib={_mkTag("xml", "space"): "preserve"})
elif segment: elif segment:
xmlSubElem(run, _wTag("t"), segment) _wText(xR, segment)
return run if fmt & X_HRF and fLink:
xmlSubElem(rPr, _wTag("rStyle"), attrib={W_VAL: "InternetLink"})
rId = self._appendExternalRel(fLink)
xH = ET.Element(_wTag("hyperlink"), attrib={_mkTag("r", "id"): rId})
xH.append(xR)
return xH
return xR
## ##
# DocX Content # DocX Content
@@ -466,12 +502,12 @@ class ToDocX(Tokenizer):
"""Generate a footnote XML object.""" """Generate a footnote XML object."""
if key in self._footnotes: if key in self._footnotes:
idx = len(self._usedNotes) + 1 idx = len(self._usedNotes) + 1
run = ET.Element(_wTag("r")) xR = ET.Element(_wTag("r"))
rPr = xmlSubElem(run, _wTag("rPr")) rPr = xmlSubElem(xR, _wTag("rPr"))
xmlSubElem(rPr, _wTag("vertAlign"), attrib={_wTag("val"): "superscript"}) xmlSubElem(rPr, _wTag("vertAlign"), attrib={W_VAL: "superscript"})
xmlSubElem(run, _wTag("footnoteReference"), attrib={_wTag("id"): str(idx)}) xmlSubElem(xR, _wTag("footnoteReference"), attrib={_wTag("id"): str(idx)})
self._usedNotes[key] = idx self._usedNotes[key] = idx
return run return xR
return None return None
def _generateStyles(self) -> None: def _generateStyles(self) -> None:
@@ -617,8 +653,8 @@ class ToDocX(Tokenizer):
basedOn=S_NORM, basedOn=S_NORM,
before=0.0, before=0.0,
after=fnSz * self._marginFoot[1], after=fnSz * self._marginFoot[1],
left=fnSz * self._marginFoot[0],
line=fnSz * self._lineHeight, line=fnSz * self._lineHeight,
hanging=fnSz * self._marginFoot[0],
)) ))
# Add to Cache # Add to Cache
@@ -629,7 +665,19 @@ class ToDocX(Tokenizer):
def _nextRelId(self) -> str: def _nextRelId(self) -> str:
"""Generate the next unique rId.""" """Generate the next unique rId."""
return f"rId{len(self._files) + 1}" return f"rId{len(self._rels) + 1}"
def _appendExternalRel(self, target: str) -> str:
"""Append external rel to the registry."""
if rel := self._rels.get(target):
return rel.rId
rId = self._nextRelId()
self._rels[target] = DocXXmlRel(
rId=rId,
relType=f"{RELS_BASE}/hyperlink",
targetMode="External"
)
return rId
def _appXml(self) -> str: def _appXml(self) -> str:
"""Populate app.xml.""" """Populate app.xml."""
@@ -637,11 +685,13 @@ class ToDocX(Tokenizer):
xRoot = ET.Element("Properties", attrib={ xRoot = ET.Element("Properties", attrib={
"xmlns": f"{OOXML_SCM}/officeDocument/2006/extended-properties" "xmlns": f"{OOXML_SCM}/officeDocument/2006/extended-properties"
}) })
self._rels["app.xml"] = DocXXmlRel(
rId=rId,
relType=f"{RELS_BASE}/extended-properties",
)
self._files["app.xml"] = DocXXmlFile( self._files["app.xml"] = DocXXmlFile(
xml=xRoot, xml=xRoot,
rId=rId,
path="docProps", path="docProps",
relType=f"{RELS_BASE}/extended-properties",
contentType="application/vnd.openxmlformats-officedocument.extended-properties+xml", contentType="application/vnd.openxmlformats-officedocument.extended-properties+xml",
) )
@@ -661,12 +711,14 @@ class ToDocX(Tokenizer):
def _coreXml(self) -> str: def _coreXml(self) -> str:
"""Populate app.xml.""" """Populate app.xml."""
rId = self._nextRelId() rId = self._nextRelId()
xRoot = ET.Element("coreProperties") xRoot = ET.Element(_mkTag("cp", "coreProperties"))
self._rels["core.xml"] = DocXXmlRel(
rId=rId,
relType=f"{OOXML_SCM}/package/2006/relationships/metadata/core-properties",
)
self._files["core.xml"] = DocXXmlFile( self._files["core.xml"] = DocXXmlFile(
xml=xRoot, xml=xRoot,
rId=rId,
path="docProps", path="docProps",
relType=f"{OOXML_SCM}/package/2006/relationships/metadata/core-properties",
contentType="application/vnd.openxmlformats-package.core-properties+xml", contentType="application/vnd.openxmlformats-package.core-properties+xml",
) )
@@ -676,7 +728,6 @@ class ToDocX(Tokenizer):
xmlSubElem(xRoot, _mkTag("dcterms", "modified"), timeStamp, attrib=tsAttr) xmlSubElem(xRoot, _mkTag("dcterms", "modified"), timeStamp, attrib=tsAttr)
xmlSubElem(xRoot, _mkTag("dc", "creator"), self._project.data.author) xmlSubElem(xRoot, _mkTag("dc", "creator"), self._project.data.author)
xmlSubElem(xRoot, _mkTag("dc", "title"), self._project.data.name) xmlSubElem(xRoot, _mkTag("dc", "title"), self._project.data.name)
xmlSubElem(xRoot, _mkTag("dc", "creator"), self._project.data.author)
xmlSubElem(xRoot, _mkTag("dc", "language"), self._dLanguage) xmlSubElem(xRoot, _mkTag("dc", "language"), self._dLanguage)
xmlSubElem(xRoot, _mkTag("cp", "revision"), str(self._project.data.saveCount)) xmlSubElem(xRoot, _mkTag("cp", "revision"), str(self._project.data.saveCount))
xmlSubElem(xRoot, _mkTag("cp", "lastModifiedBy"), self._project.data.author) xmlSubElem(xRoot, _mkTag("cp", "lastModifiedBy"), self._project.data.author)
@@ -687,11 +738,13 @@ class ToDocX(Tokenizer):
"""Populate styles.xml.""" """Populate styles.xml."""
rId = self._nextRelId() rId = self._nextRelId()
xRoot = ET.Element(_wTag("styles")) xRoot = ET.Element(_wTag("styles"))
self._rels["styles.xml"] = DocXXmlRel(
rId=rId,
relType=f"{RELS_BASE}/styles",
)
self._files["styles.xml"] = DocXXmlFile( self._files["styles.xml"] = DocXXmlFile(
xml=xRoot, xml=xRoot,
rId=rId,
path="word", path="word",
relType=f"{RELS_BASE}/styles",
contentType=f"{WORD_BASE}.styles+xml", contentType=f"{WORD_BASE}.styles+xml",
) )
@@ -710,9 +763,9 @@ class ToDocX(Tokenizer):
_wTag("hAnsi"): self._fontFamily, _wTag("hAnsi"): self._fontFamily,
_wTag("cs"): self._fontFamily, _wTag("cs"): self._fontFamily,
}) })
xmlSubElem(xRPr, _wTag("sz"), attrib={_wTag("val"): size}) xmlSubElem(xRPr, _wTag("sz"), attrib={W_VAL: size})
xmlSubElem(xRPr, _wTag("szCs"), attrib={_wTag("val"): size}) xmlSubElem(xRPr, _wTag("szCs"), attrib={W_VAL: size})
xmlSubElem(xRPr, _wTag("lang"), attrib={_wTag("val"): self._dLanguage}) xmlSubElem(xRPr, _wTag("lang"), attrib={W_VAL: self._dLanguage})
xmlSubElem(xPPr, _wTag("spacing"), attrib={_wTag("line"): line}) xmlSubElem(xPPr, _wTag("spacing"), attrib={_wTag("line"): line})
# Paragraph Styles # Paragraph Styles
@@ -726,11 +779,11 @@ class ToDocX(Tokenizer):
size = firstFloat(style.size, self._fontSize) size = firstFloat(style.size, self._fontSize)
xStyl = xmlSubElem(xRoot, _wTag("style"), attrib=sAttr) xStyl = xmlSubElem(xRoot, _wTag("style"), attrib=sAttr)
xmlSubElem(xStyl, _wTag("name"), attrib={_wTag("val"): style.name}) xmlSubElem(xStyl, _wTag("name"), attrib={W_VAL: style.name})
if style.basedOn: if style.basedOn:
xmlSubElem(xStyl, _wTag("basedOn"), attrib={_wTag("val"): style.basedOn}) xmlSubElem(xStyl, _wTag("basedOn"), attrib={W_VAL: style.basedOn})
if style.nextStyle: if style.nextStyle:
xmlSubElem(xStyl, _wTag("next"), attrib={_wTag("val"): style.nextStyle}) xmlSubElem(xStyl, _wTag("next"), attrib={W_VAL: style.nextStyle})
# pPr Node # pPr Node
pPr = xmlSubElem(xStyl, _wTag("pPr")) pPr = xmlSubElem(xStyl, _wTag("pPr"))
@@ -739,24 +792,33 @@ class ToDocX(Tokenizer):
_wTag("after"): str(int(20.0 * firstFloat(style.after))), _wTag("after"): str(int(20.0 * firstFloat(style.after))),
_wTag("line"): str(int(20.0 * firstFloat(style.line, size))), _wTag("line"): str(int(20.0 * firstFloat(style.line, size))),
}) })
if style.hanging is not None: if style.left is not None:
xmlSubElem(pPr, _wTag("ind"), attrib={ xmlSubElem(pPr, _wTag("ind"), attrib={
_wTag("left"): str(int(20.0 * style.hanging)), _wTag("left"): str(int(20.0 * style.left)),
_wTag("hanging"): str(int(20.0 * style.hanging)),
}) })
if style.align: if style.align:
xmlSubElem(pPr, _wTag("jc"), attrib={_wTag("val"): style.align}) xmlSubElem(pPr, _wTag("jc"), attrib={W_VAL: style.align})
if style.level is not None: if style.level is not None:
xmlSubElem(pPr, _wTag("outlineLvl"), attrib={_wTag("val"): str(style.level)}) xmlSubElem(pPr, _wTag("outlineLvl"), attrib={W_VAL: str(style.level)})
# rPr Node # rPr Node
rPr = xmlSubElem(xStyl, _wTag("rPr")) rPr = xmlSubElem(xStyl, _wTag("rPr"))
if style.bold: if style.bold:
xmlSubElem(rPr, _wTag("b")) xmlSubElem(rPr, _wTag("b"))
if style.color: if style.color:
xmlSubElem(rPr, _wTag("color"), attrib={_wTag("val"): style.color}) xmlSubElem(rPr, _wTag("color"), attrib={W_VAL: style.color})
xmlSubElem(rPr, _wTag("sz"), attrib={_wTag("val"): str(int(2.0 * size))}) xmlSubElem(rPr, _wTag("sz"), attrib={W_VAL: str(int(2.0 * size))})
xmlSubElem(rPr, _wTag("szCs"), attrib={_wTag("val"): str(int(2.0 * size))}) xmlSubElem(rPr, _wTag("szCs"), attrib={W_VAL: str(int(2.0 * size))})
# Character Style
xStyl = xmlSubElem(xRoot, _wTag("style"), attrib={
_wTag("type"): "character",
_wTag("styleId"): "InternetLink"
})
xmlSubElem(xStyl, _wTag("name"), attrib={W_VAL: "Hyperlink"})
rPr = xmlSubElem(xStyl, _wTag("rPr"))
xmlSubElem(rPr, _wTag("color"), attrib={W_VAL: _docXCol(self._theme.link)})
xmlSubElem(rPr, _wTag("u"), attrib={W_VAL: "single"})
return rId return rId
@@ -764,18 +826,20 @@ class ToDocX(Tokenizer):
"""Populate header1.xml.""" """Populate header1.xml."""
rId = self._nextRelId() rId = self._nextRelId()
xRoot = ET.Element(_wTag("hdr")) xRoot = ET.Element(_wTag("hdr"))
self._rels["header1.xml"] = DocXXmlRel(
rId=rId,
relType=f"{RELS_BASE}/header",
)
self._files["header1.xml"] = DocXXmlFile( self._files["header1.xml"] = DocXXmlFile(
xml=xRoot, xml=xRoot,
rId=rId,
path="word", path="word",
relType=f"{RELS_BASE}/header",
contentType=f"{WORD_BASE}.header+xml", contentType=f"{WORD_BASE}.header+xml",
) )
xP = xmlSubElem(xRoot, _wTag("p")) xP = xmlSubElem(xRoot, _wTag("p"))
xPPr = xmlSubElem(xP, _wTag("pPr")) xPPr = xmlSubElem(xP, _wTag("pPr"))
xmlSubElem(xPPr, _wTag("pStyle"), attrib={_wTag("val"): S_HEAD}) xmlSubElem(xPPr, _wTag("pStyle"), attrib={W_VAL: S_HEAD})
xmlSubElem(xPPr, _wTag("jc"), attrib={_wTag("val"): "right"}) xmlSubElem(xPPr, _wTag("jc"), attrib={W_VAL: "right"})
xmlSubElem(xPPr, _wTag("rPr")) xmlSubElem(xPPr, _wTag("rPr"))
pre, page, post = self._headerFormat.partition(nwHeadFmt.DOC_PAGE) pre, page, post = self._headerFormat.partition(nwHeadFmt.DOC_PAGE)
@@ -784,25 +848,24 @@ class ToDocX(Tokenizer):
post = post.replace(nwHeadFmt.DOC_PROJECT, self._project.data.name) post = post.replace(nwHeadFmt.DOC_PROJECT, self._project.data.name)
post = post.replace(nwHeadFmt.DOC_AUTHOR, self._project.data.author) post = post.replace(nwHeadFmt.DOC_AUTHOR, self._project.data.author)
xSpace = _mkTag("xml", "space")
wFldCT = _wTag("fldCharType") wFldCT = _wTag("fldCharType")
parts: list[tuple[str, str | None, str, str]] = []
if pre: if pre:
parts.append(("t", pre, xSpace, "preserve"))
if page:
parts.append(("fldChar", None, wFldCT, "begin"))
parts.append(("t", " PAGE ", xSpace, "preserve"))
parts.append(("fldChar", None, wFldCT, "separate"))
parts.append(("t", "2", xSpace, "preserve"))
parts.append(("fldChar", None, wFldCT, "end"))
if post:
parts.append(("t", post, xSpace, "preserve"))
for part in parts:
xR = xmlSubElem(xP, _wTag("r")) xR = xmlSubElem(xP, _wTag("r"))
xmlSubElem(xR, _wTag("rPr")) _wText(xR, pre)
xmlSubElem(xR, _wTag(part[0]), part[1], attrib={part[2]: part[3]}) if page:
xR = xmlSubElem(xP, _wTag("r"))
xmlSubElem(xR, _wTag("fldChar"), attrib={wFldCT: "begin"})
xR = xmlSubElem(xP, _wTag("r"))
_wText(xR, " PAGE ")
xR = xmlSubElem(xP, _wTag("r"))
xmlSubElem(xR, _wTag("fldChar"), attrib={wFldCT: "separate"})
xR = xmlSubElem(xP, _wTag("r"))
_wText(xR, "0")
xR = xmlSubElem(xP, _wTag("r"))
xmlSubElem(xR, _wTag("fldChar"), attrib={wFldCT: "end"})
if post:
xR = xmlSubElem(xP, _wTag("r"))
_wText(xR, post)
return rId return rId
@@ -810,18 +873,20 @@ class ToDocX(Tokenizer):
"""Populate header2.xml.""" """Populate header2.xml."""
rId = self._nextRelId() rId = self._nextRelId()
xRoot = ET.Element(_wTag("hdr")) xRoot = ET.Element(_wTag("hdr"))
self._rels["header2.xml"] = DocXXmlRel(
rId=rId,
relType=f"{RELS_BASE}/header",
)
self._files["header2.xml"] = DocXXmlFile( self._files["header2.xml"] = DocXXmlFile(
xml=xRoot, xml=xRoot,
rId=rId,
path="word", path="word",
relType=f"{RELS_BASE}/header",
contentType=f"{WORD_BASE}.header+xml", contentType=f"{WORD_BASE}.header+xml",
) )
xP = xmlSubElem(xRoot, _wTag("p")) xP = xmlSubElem(xRoot, _wTag("p"))
xPPr = xmlSubElem(xP, _wTag("pPr")) xPPr = xmlSubElem(xP, _wTag("pPr"))
xmlSubElem(xPPr, _wTag("pStyle"), attrib={_wTag("val"): S_HEAD}) xmlSubElem(xPPr, _wTag("pStyle"), attrib={W_VAL: S_HEAD})
xmlSubElem(xPPr, _wTag("jc"), attrib={_wTag("val"): "right"}) xmlSubElem(xPPr, _wTag("jc"), attrib={W_VAL: "right"})
xmlSubElem(xPPr, _wTag("rPr")) xmlSubElem(xPPr, _wTag("rPr"))
xR = xmlSubElem(xP, _wTag("r")) xR = xmlSubElem(xP, _wTag("r"))
@@ -833,12 +898,15 @@ class ToDocX(Tokenizer):
"""Populate document.xml.""" """Populate document.xml."""
rId = self._nextRelId() rId = self._nextRelId()
xRoot = ET.Element(_wTag("document")) xRoot = ET.Element(_wTag("document"))
xRoot.set("xmlns:w14", "http://schemas.microsoft.com/office/word/2010/wordml")
xBody = xmlSubElem(xRoot, _wTag("body")) xBody = xmlSubElem(xRoot, _wTag("body"))
self._rels["document.xml"] = DocXXmlRel(
rId=rId,
relType=f"{RELS_BASE}/officeDocument",
)
self._files["document.xml"] = DocXXmlFile( self._files["document.xml"] = DocXXmlFile(
xml=xRoot, xml=xRoot,
rId=rId,
path="word", path="word",
relType=f"{RELS_BASE}/officeDocument",
contentType=f"{WORD_BASE}.document.main+xml", contentType=f"{WORD_BASE}.document.main+xml",
) )
@@ -871,7 +939,7 @@ class ToDocX(Tokenizer):
xFn = xmlSubElem(xSect, _wTag("footnotePr")) xFn = xmlSubElem(xSect, _wTag("footnotePr"))
xmlSubElem(xFn, _wTag("numFmt"), attrib={ xmlSubElem(xFn, _wTag("numFmt"), attrib={
_wTag("val"): "decimal", W_VAL: "decimal",
}) })
xmlSubElem(xSect, _wTag("pgSz"), attrib={ xmlSubElem(xSect, _wTag("pgSz"), attrib={
@@ -900,43 +968,78 @@ class ToDocX(Tokenizer):
"""Populate footnotes.xml.""" """Populate footnotes.xml."""
rId = self._nextRelId() rId = self._nextRelId()
xRoot = ET.Element(_wTag("footnotes")) xRoot = ET.Element(_wTag("footnotes"))
self._rels["footnotes.xml"] = DocXXmlRel(
rId=rId,
relType=f"{RELS_BASE}/footnotes",
)
self._files["footnotes.xml"] = DocXXmlFile( self._files["footnotes.xml"] = DocXXmlFile(
xml=xRoot, xml=xRoot,
rId=rId,
path="word", path="word",
relType=f"{RELS_BASE}/footnotes",
contentType=f"{WORD_BASE}.footnotes+xml", contentType=f"{WORD_BASE}.footnotes+xml",
) )
for key, idx in self._usedNotes.items(): for key, idx in self._usedNotes.items():
par = DocXParagraph() par = DocXParagraph()
par.setIsFootnote(True)
if content := self._footnotes.get(key): if content := self._footnotes.get(key):
self._processFragments(par, S_FNOTE, content[0], content[1]) self._processFragments(par, S_FNOTE, content[0], content[1])
par.toXml(xmlSubElem(xRoot, _wTag("footnote"), attrib={_wTag("id"): str(idx)})) par.toXml(xmlSubElem(xRoot, _wTag("footnote"), attrib={_wTag("id"): str(idx)}))
return rId return rId
def _fontTableXml(self) -> str:
"""Populate fontTable.xml."""
rId = self._nextRelId()
xRoot = ET.Element(_wTag("fonts"))
self._rels["fontTable.xml"] = DocXXmlRel(
rId=rId,
relType=f"{RELS_BASE}/fontTable",
)
self._files["fontTable.xml"] = DocXXmlFile(
xml=xRoot,
path="word",
contentType=f"{WORD_BASE}.fontTable+xml",
)
xFont = xmlSubElem(xRoot, _wTag("font"), attrib={
_wTag("name"): self._textFont.family(),
})
xmlSubElem(xFont, _wTag("pitch"), attrib={
W_VAL: "fixed" if self._textFont.fixedPitch() else "variable",
})
return rId
def _settingsXml(self) -> str: def _settingsXml(self) -> str:
"""Populate settings.xml.""" """Populate settings.xml."""
rId = self._nextRelId() rId = self._nextRelId()
xRoot = ET.Element(_wTag("settings")) xRoot = ET.Element(_wTag("settings"))
self._rels["settings.xml"] = DocXXmlRel(
rId=rId,
relType=f"{RELS_BASE}/settings",
)
self._files["settings.xml"] = DocXXmlFile( self._files["settings.xml"] = DocXXmlFile(
xml=xRoot, xml=xRoot,
rId=rId,
path="word", path="word",
relType=f"{RELS_BASE}/settings",
contentType=f"{WORD_BASE}.settings+xml", contentType=f"{WORD_BASE}.settings+xml",
) )
xFn = xmlSubElem(xRoot, _wTag("footnotePr")) xSet = xmlSubElem(xRoot, _wTag("footnotePr"))
xmlSubElem(xFn, _wTag("numFmt"), attrib={_wTag("val"): "decimal"}) xmlSubElem(xSet, _wTag("numFmt"), attrib={W_VAL: "decimal"})
xSet = xmlSubElem(xRoot, _wTag("compat"))
xmlSubElem(xSet, _wTag("compatSetting"), attrib={
_wTag("name"): "compatibilityMode",
_wTag("uri"): "http://schemas.microsoft.com/office/word",
W_VAL: "12",
})
if self._counts: if self._counts:
xVars = xmlSubElem(xRoot, _wTag("docVars")) xVars = xmlSubElem(xRoot, _wTag("docVars"))
for key, value in self._counts.items(): for key, value in self._counts.items():
xmlSubElem(xVars, _wTag("docVar"), attrib={ xmlSubElem(xVars, _wTag("docVar"), attrib={
_wTag("name"): f"Manuscript{key[:1].upper()}{key[1:]}", _wTag("name"): f"Manuscript{key[:1].upper()}{key[1:]}",
_wTag("val"): str(value), W_VAL: str(value),
}) })
return rId return rId
@@ -947,7 +1050,7 @@ class DocXParagraph:
__slots__ = ( __slots__ = (
"_content", "_style", "_textAlign", "_content", "_style", "_textAlign",
"_topMargin", "_bottomMargin", "_leftMargin", "_rightMargin", "_topMargin", "_bottomMargin", "_leftMargin", "_rightMargin",
"_indentFirst", "_breakBefore", "_breakAfter", "_indentFirst", "_breakBefore", "_breakAfter", "_footnoteRef",
) )
def __init__(self) -> None: def __init__(self) -> None:
@@ -961,6 +1064,7 @@ class DocXParagraph:
self._indentFirst = False self._indentFirst = False
self._breakBefore = False self._breakBefore = False
self._breakAfter = False self._breakAfter = False
self._footnoteRef = False
return return
## ##
@@ -1022,6 +1126,11 @@ class DocXParagraph:
self._breakAfter = state self._breakAfter = state
return return
def setIsFootnote(self, state: bool) -> None:
"""Set is footnote flag."""
self._footnoteRef = state
return
## ##
# Methods # Methods
## ##
@@ -1034,7 +1143,7 @@ class DocXParagraph:
def toXml(self, body: ET.Element) -> None: def toXml(self, body: ET.Element) -> None:
"""Called after all content is set.""" """Called after all content is set."""
if style := self._style: if style := self._style:
par = xmlSubElem(body, _wTag("p")) xP = xmlSubElem(body, _wTag("p"))
# Values # Values
indent = {} indent = {}
@@ -1046,27 +1155,32 @@ class DocXParagraph:
indent[_wTag("right")] = str(int(20.0 * self._rightMargin)) indent[_wTag("right")] = str(int(20.0 * self._rightMargin))
# Paragraph # Paragraph
pPr = xmlSubElem(par, _wTag("pPr")) pPr = xmlSubElem(xP, _wTag("pPr"))
xmlSubElem(pPr, _wTag("pStyle"), attrib={_wTag("val"): style.styleId}) xmlSubElem(pPr, _wTag("pStyle"), attrib={W_VAL: style.styleId})
if self._topMargin is not None or self._bottomMargin is not None: if self._topMargin is not None or self._bottomMargin is not None:
xmlSubElem(pPr, _wTag("spacing"), attrib={ xmlSubElem(pPr, _wTag("spacing"), attrib={
_wTag("before"): str(int(20.0 * firstFloat(self._topMargin, style.before))), _wTag("before"): str(int(20.0 * firstFloat(self._topMargin, style.before))),
_wTag("after"): str(int(20.0 * firstFloat(self._bottomMargin, style.after))), _wTag("after"): str(int(20.0 * firstFloat(self._bottomMargin, style.after))),
_wTag("line"): str(int(20.0 * firstFloat(style.line, style.size))), _wTag("line"): str(int(20.0 * firstFloat(style.line, style.size))),
}) })
if self._textAlign:
xmlSubElem(pPr, _wTag("jc"), attrib={_wTag("val"): self._textAlign})
if indent: if indent:
xmlSubElem(pPr, _wTag("ind"), attrib=indent) xmlSubElem(pPr, _wTag("ind"), attrib=indent)
if self._textAlign:
xmlSubElem(pPr, _wTag("jc"), attrib={W_VAL: self._textAlign})
# Text # Text
if self._footnoteRef:
xR = xmlSubElem(xP, _wTag("r"))
rPr = xmlSubElem(xR, _wTag("rPr"))
xmlSubElem(rPr, _wTag("vertAlign"), attrib={W_VAL: "superscript"})
xmlSubElem(xR, _wTag("footnoteRef"))
if self._breakBefore: if self._breakBefore:
wr = xmlSubElem(par, _wTag("r")) xR = xmlSubElem(xP, _wTag("r"))
xmlSubElem(wr, _wTag("br"), attrib={_wTag("type"): "page"}) xmlSubElem(xR, _wTag("br"), attrib={_wTag("type"): "page"})
for run in self._content: for xR in self._content:
par.append(run) xP.append(xR)
if self._breakAfter: if self._breakAfter:
wr = xmlSubElem(par, _wTag("r")) xR = xmlSubElem(xP, _wTag("r"))
xmlSubElem(wr, _wTag("br"), attrib={_wTag("type"): "page"}) xmlSubElem(xR, _wTag("br"), attrib={_wTag("type"): "page"})
return return
+3 -1
View File
@@ -49,6 +49,7 @@ HTML_OPENER: dict[int, tuple[int, str]] = {
TextFmt.SUB_B: (TextFmt.SUB_E, "<sub>"), TextFmt.SUB_B: (TextFmt.SUB_E, "<sub>"),
TextFmt.COL_B: (TextFmt.COL_E, "<span style='color: {0}'>"), TextFmt.COL_B: (TextFmt.COL_E, "<span style='color: {0}'>"),
TextFmt.ANM_B: (TextFmt.ANM_E, "<a name='{0}'>"), TextFmt.ANM_B: (TextFmt.ANM_E, "<a name='{0}'>"),
TextFmt.ARF_B: (TextFmt.ARF_E, "<a href='{0}'>"),
TextFmt.HRF_B: (TextFmt.HRF_E, "<a href='{0}'>"), TextFmt.HRF_B: (TextFmt.HRF_E, "<a href='{0}'>"),
} }
@@ -63,6 +64,7 @@ HTML_CLOSER: dict[int, tuple[int, str]] = {
TextFmt.SUB_E: (TextFmt.SUB_B, "</sub>"), TextFmt.SUB_E: (TextFmt.SUB_B, "</sub>"),
TextFmt.COL_E: (TextFmt.COL_B, "</span>"), TextFmt.COL_E: (TextFmt.COL_B, "</span>"),
TextFmt.ANM_E: (TextFmt.ANM_B, "</a>"), TextFmt.ANM_E: (TextFmt.ANM_B, "</a>"),
TextFmt.ARF_E: (TextFmt.ARF_B, "</a>"),
TextFmt.HRF_E: (TextFmt.HRF_B, "</a>"), TextFmt.HRF_E: (TextFmt.HRF_B, "</a>"),
} }
@@ -398,7 +400,7 @@ class ToHtml(Tokenizer):
if not state.get(fmt, True): if not state.get(fmt, True):
if fmt == TextFmt.COL_B and (color := self._classes.get(data)): if fmt == TextFmt.COL_B and (color := self._classes.get(data)):
tags.append((pos, m[1].format(color.name(QtHexRgb)))) tags.append((pos, m[1].format(color.name(QtHexRgb))))
elif fmt in (TextFmt.ANM_B, TextFmt.HRF_B): elif fmt in (TextFmt.ANM_B, TextFmt.ARF_B, TextFmt.HRF_B):
tags.append((pos, m[1].format(data or "#"))) tags.append((pos, m[1].format(data or "#")))
else: else:
tags.append((pos, m[1])) tags.append((pos, m[1]))
+9 -2
View File
@@ -1089,8 +1089,8 @@ class Tokenizer(ABC):
for n, bit in enumerate(bits[1:], 2): for n, bit in enumerate(bits[1:], 2):
end = pos + len(bit) end = pos + len(bit)
fmt.append((pos, TextFmt.COL_B, "tag")) fmt.append((pos, TextFmt.COL_B, "tag"))
fmt.append((pos, TextFmt.HRF_B, f"#tag_{bit}".lower())) fmt.append((pos, TextFmt.ARF_B, f"#tag_{bit}".lower()))
fmt.append((end, TextFmt.HRF_E, "")) fmt.append((end, TextFmt.ARF_E, ""))
fmt.append((end, TextFmt.COL_E, "")) fmt.append((end, TextFmt.COL_E, ""))
txt.append(bit) txt.append(bit)
pos = end pos = end
@@ -1117,6 +1117,13 @@ class Tokenizer(ABC):
for n, fmt in enumerate(fmts) if fmt > 0 for n, fmt in enumerate(fmts) if fmt > 0
) )
# Match URLs
for res in REGEX_PATTERNS.url.finditer(text):
s = res.start(0)
e = res.end(0)
temp.append((s, s, TextFmt.HRF_B, res.group(0)))
temp.append((e, e, TextFmt.HRF_E, ""))
# Match Shortcodes # Match Shortcodes
for res in REGEX_PATTERNS.shortcodePlain.finditer(text): for res in REGEX_PATTERNS.shortcodePlain.finditer(text):
temp.append(( temp.append((
+33 -10
View File
@@ -49,14 +49,15 @@ logger = logging.getLogger(__name__)
# Main XML NameSpaces # Main XML NameSpaces
XML_NS = { XML_NS = {
"dc": "http://purl.org/dc/elements/1.1/",
"fo": "urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0",
"loext": "urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0",
"manifest": "urn:oasis:names:tc:opendocument:xmlns:manifest:1.0", "manifest": "urn:oasis:names:tc:opendocument:xmlns:manifest:1.0",
"meta": "urn:oasis:names:tc:opendocument:xmlns:meta:1.0",
"office": "urn:oasis:names:tc:opendocument:xmlns:office:1.0", "office": "urn:oasis:names:tc:opendocument:xmlns:office:1.0",
"style": "urn:oasis:names:tc:opendocument:xmlns:style:1.0", "style": "urn:oasis:names:tc:opendocument:xmlns:style:1.0",
"loext": "urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0",
"text": "urn:oasis:names:tc:opendocument:xmlns:text:1.0", "text": "urn:oasis:names:tc:opendocument:xmlns:text:1.0",
"meta": "urn:oasis:names:tc:opendocument:xmlns:meta:1.0", "xlink": "http://www.w3.org/1999/xlink",
"fo": "urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0",
"dc": "http://purl.org/dc/elements/1.1/",
} }
for ns, uri in XML_NS.items(): for ns, uri in XML_NS.items():
ET.register_namespace(ns, uri) ET.register_namespace(ns, uri)
@@ -80,7 +81,6 @@ TAG_SPC = _mkTag("text", "s")
TAG_NSPC = _mkTag("text", "c") TAG_NSPC = _mkTag("text", "c")
TAG_TAB = _mkTag("text", "tab") TAG_TAB = _mkTag("text", "tab")
TAG_SPAN = _mkTag("text", "span") TAG_SPAN = _mkTag("text", "span")
TAG_STNM = _mkTag("text", "style-name")
# Formatting Codes # Formatting Codes
X_BLD = 0x001 # Bold format X_BLD = 0x001 # Bold format
@@ -91,6 +91,7 @@ X_MRK = 0x010 # Marked format
X_SUP = 0x020 # Superscript X_SUP = 0x020 # Superscript
X_SUB = 0x040 # Subscript X_SUB = 0x040 # Subscript
X_COL = 0x080 # Coloured text X_COL = 0x080 # Coloured text
X_HRF = 0x100 # Link
# Formatting Masks # Formatting Masks
M_BLD = ~X_BLD M_BLD = ~X_BLD
@@ -101,6 +102,7 @@ M_MRK = ~X_MRK
M_SUP = ~X_SUP M_SUP = ~X_SUP
M_SUB = ~X_SUB M_SUB = ~X_SUB
M_COL = ~X_COL M_COL = ~X_COL
M_HRF = ~X_HRF
# ODT Styles # ODT Styles
S_TITLE = "Title" S_TITLE = "Title"
@@ -570,6 +572,7 @@ class ToOdt(Tokenizer):
fLast = 0 fLast = 0
xNode = None xNode = None
fClass = "" fClass = ""
fLink = ""
for fPos, fFmt, fData in tFmt or []: for fPos, fFmt, fData in tFmt or []:
# Add any extra nodes # Add any extra nodes
@@ -582,7 +585,7 @@ class ToOdt(Tokenizer):
if xFmt == 0x00: if xFmt == 0x00:
parProc.appendText(tFrag) parProc.appendText(tFrag)
else: else:
parProc.appendSpan(tFrag, self._textStyle(xFmt, fClass)) parProc.appendSpan(tFrag, self._textStyle(xFmt, fClass), fLink)
# Calculate the change of format # Calculate the change of format
if fFmt == TextFmt.B_B: if fFmt == TextFmt.B_B:
@@ -619,6 +622,12 @@ class ToOdt(Tokenizer):
elif fFmt == TextFmt.COL_E: elif fFmt == TextFmt.COL_E:
xFmt &= M_COL xFmt &= M_COL
fClass = "" fClass = ""
elif fFmt == TextFmt.HRF_B:
xFmt |= X_HRF
fLink = fData
elif fFmt == TextFmt.HRF_E:
xFmt &= M_HRF
fLink = ""
elif fFmt == TextFmt.FNOTE: elif fFmt == TextFmt.FNOTE:
xNode = self._generateFootnote(fData) xNode = self._generateFootnote(fData)
elif fFmt == TextFmt.STRIP: elif fFmt == TextFmt.STRIP:
@@ -633,7 +642,7 @@ class ToOdt(Tokenizer):
if xFmt == 0x00: if xFmt == 0x00:
parProc.appendText(tFrag) parProc.appendText(tFrag)
else: else:
parProc.appendSpan(tFrag, self._textStyle(xFmt, fClass)) parProc.appendSpan(tFrag, self._textStyle(xFmt, fClass), fLink)
nErr, errMsg = parProc.checkError() nErr, errMsg = parProc.checkError()
if nErr > 0: # pragma: no cover if nErr > 0: # pragma: no cover
@@ -692,6 +701,11 @@ class ToOdt(Tokenizer):
style.setTextPosition("sub") style.setTextPosition("sub")
if hFmt & X_COL and color: if hFmt & X_COL and color:
style.setColor(color) style.setColor(color)
if hFmt & X_HRF:
style.setColor(self._theme.link)
style.setUnderlineStyle("solid")
style.setUnderlineWidth("auto")
style.setUnderlineColor("font-color")
self._autoText[tKey] = style self._autoText[tKey] = style
return style.name return style.name
@@ -1497,13 +1511,22 @@ class XMLParagraph:
return return
def appendSpan(self, text: str, fmt: str) -> None: def appendSpan(self, text: str, style: str, link: str) -> None:
"""Append a text span to the XML element. The span is always """Append a text span to the XML element. The span is always
closed since we do not allow nested spans (like Libre Office). closed since we do not produce nested spans (like Libre Office).
Therefore we return to the root element level when we're done Therefore we return to the root element level when we're done
processing the text of the span. processing the text of the span.
""" """
self._xTail = ET.SubElement(self._xRoot, TAG_SPAN, attrib={TAG_STNM: fmt}) if link:
self._xTail = ET.SubElement(self._xRoot, _mkTag("text", "a"), attrib={
_mkTag("xlink", "type"): "simple",
_mkTag("xlink", "href"): link,
_mkTag("text", "style-name"): style,
})
else:
self._xTail = ET.SubElement(self._xRoot, TAG_SPAN, attrib={
_mkTag("text", "style-name"): style,
})
self._xTail.text = "" # Defaults to None self._xTail.text = "" # Defaults to None
self._xTail.tail = "" # Defaults to None self._xTail.tail = "" # Defaults to None
self._nState = X_SPAN_TEXT self._nState = X_SPAN_TEXT
+16 -2
View File
@@ -29,7 +29,7 @@ from pathlib import Path
from PyQt5.QtCore import QMarginsF, QSizeF from PyQt5.QtCore import QMarginsF, QSizeF
from PyQt5.QtGui import ( from PyQt5.QtGui import (
QFont, QFontMetricsF, QPageSize, QTextBlockFormat, QTextCharFormat, QColor, QFont, QFontMetricsF, QPageSize, QTextBlockFormat, QTextCharFormat,
QTextCursor, QTextDocument QTextCursor, QTextDocument
) )
from PyQt5.QtPrintSupport import QPrinter from PyQt5.QtPrintSupport import QPrinter
@@ -281,8 +281,9 @@ class ToQTextDocument(Tokenizer):
) -> None: ) -> None:
"""Apply formatting tags to text.""" """Apply formatting tags to text."""
cFmt = QTextCharFormat(dFmt) cFmt = QTextCharFormat(dFmt)
start = 0
temp = text.replace("\n", nwUnicode.U_LSEP) temp = text.replace("\n", nwUnicode.U_LSEP)
start = 0
primary: QColor | None = None
for pos, fmt, data in tFmt: for pos, fmt, data in tFmt:
# Insert buffer with previous format # Insert buffer with previous format
@@ -320,20 +321,33 @@ class ToQTextDocument(Tokenizer):
elif fmt == TextFmt.COL_B: elif fmt == TextFmt.COL_B:
if color := self._classes.get(data): if color := self._classes.get(data):
cFmt.setForeground(color) cFmt.setForeground(color)
primary = color
elif fmt == TextFmt.COL_E: elif fmt == TextFmt.COL_E:
cFmt.setForeground(self._theme.text) cFmt.setForeground(self._theme.text)
primary = None
elif fmt == TextFmt.ANM_B: elif fmt == TextFmt.ANM_B:
cFmt.setAnchor(True) cFmt.setAnchor(True)
cFmt.setAnchorNames([data]) cFmt.setAnchorNames([data])
elif fmt == TextFmt.ANM_E: elif fmt == TextFmt.ANM_E:
cFmt.setAnchor(False) cFmt.setAnchor(False)
elif fmt == TextFmt.ARF_B:
cFmt.setFontUnderline(True)
cFmt.setAnchor(True)
cFmt.setAnchorHref(data)
elif fmt == TextFmt.ARF_E:
cFmt.setFontUnderline(False)
cFmt.setAnchor(False)
cFmt.setAnchorHref("")
elif fmt == TextFmt.HRF_B: elif fmt == TextFmt.HRF_B:
cFmt.setForeground(self._theme.link)
cFmt.setFontUnderline(True) cFmt.setFontUnderline(True)
cFmt.setAnchor(True) cFmt.setAnchor(True)
cFmt.setAnchorHref(data) cFmt.setAnchorHref(data)
elif fmt == TextFmt.HRF_E: elif fmt == TextFmt.HRF_E:
cFmt.setForeground(primary or self._theme.text)
cFmt.setFontUnderline(False) cFmt.setFontUnderline(False)
cFmt.setAnchor(False) cFmt.setAnchor(False)
cFmt.setAnchorHref("")
elif fmt == TextFmt.FNOTE: elif fmt == TextFmt.FNOTE:
xFmt = QTextCharFormat(self._charFmt) xFmt = QTextCharFormat(self._charFmt)
xFmt.setForeground(self._theme.code) xFmt.setForeground(self._theme.code)
+25 -6
View File
@@ -38,12 +38,13 @@ from enum import Enum
from time import time from time import time
from PyQt5.QtCore import ( from PyQt5.QtCore import (
QObject, QPoint, QRegularExpression, QRunnable, Qt, QTimer, pyqtSignal, QObject, QPoint, QRegularExpression, QRunnable, Qt, QTimer, QUrl,
pyqtSlot pyqtSignal, pyqtSlot
) )
from PyQt5.QtGui import ( from PyQt5.QtGui import (
QColor, QCursor, QKeyEvent, QKeySequence, QMouseEvent, QPalette, QPixmap, QColor, QCursor, QDesktopServices, QKeyEvent, QKeySequence, QMouseEvent,
QResizeEvent, QTextBlock, QTextCursor, QTextDocument, QTextOption QPalette, QPixmap, QResizeEvent, QTextBlock, QTextCursor, QTextDocument,
QTextOption
) )
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QAction, QApplication, QFrame, QGridLayout, QHBoxLayout, QLabel, QLineEdit, QAction, QApplication, QFrame, QGridLayout, QHBoxLayout, QLabel, QLineEdit,
@@ -984,8 +985,13 @@ class GuiDocEditor(QPlainTextEdit):
pressed, check if we're clicking on a tag, and trigger the pressed, check if we're clicking on a tag, and trigger the
follow tag function. follow tag function.
""" """
if QApplication.keyboardModifiers() == QtModCtrl: if event.modifiers() & QtModCtrl == QtModCtrl:
self._processTag(self.cursorForPosition(event.pos())) cursor = self.cursorForPosition(event.pos())
mData, mType = self._qDocument.metaDataAtPos(cursor.position())
if mData and mType == "url":
self._openWebsite(mData)
else:
self._processTag(cursor)
super().mouseReleaseEvent(event) super().mouseReleaseEvent(event)
return return
@@ -1116,6 +1122,13 @@ class GuiDocEditor(QPlainTextEdit):
action = ctxMenu.addAction(self.tr("Set as Document Name")) action = ctxMenu.addAction(self.tr("Set as Document Name"))
action.triggered.connect(lambda: self._emitRenameItem(pBlock)) action.triggered.connect(lambda: self._emitRenameItem(pBlock))
# URL
(mData, mType) = self._qDocument.metaDataAtPos(pCursor.position())
if mData and mType == "url":
action = ctxMenu.addAction(self.tr("Open URL"))
action.triggered.connect(lambda: self._openWebsite(mData))
ctxMenu.addSeparator()
# Follow # Follow
status = self._processTag(cursor=pCursor, follow=False) status = self._processTag(cursor=pCursor, follow=False)
if status == nwTrinary.POSITIVE: if status == nwTrinary.POSITIVE:
@@ -1183,6 +1196,12 @@ class GuiDocEditor(QPlainTextEdit):
return return
@pyqtSlot(str)
def _openWebsite(self, url: str) -> None:
"""Open a URL in the system's default browser."""
QDesktopServices.openUrl(QUrl(url))
return
@pyqtSlot() @pyqtSlot()
def _runDocumentTasks(self) -> None: def _runDocumentTasks(self) -> None:
"""Run timer document tasks.""" """Run timer document tasks."""
+46 -9
View File
@@ -44,6 +44,7 @@ from novelwriter.text.patterns import REGEX_PATTERNS
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
RX_URL = REGEX_PATTERNS.url
RX_WORDS = REGEX_PATTERNS.wordSplit RX_WORDS = REGEX_PATTERNS.wordSplit
RX_FMT_SC = REGEX_PATTERNS.shortcodePlain RX_FMT_SC = REGEX_PATTERNS.shortcodePlain
RX_FMT_SV = REGEX_PATTERNS.shortcodeValue RX_FMT_SV = REGEX_PATTERNS.shortcodeValue
@@ -113,10 +114,11 @@ class GuiDocHighlighter(QSyntaxHighlighter):
self._addCharFormat("replace", SHARED.theme.colRepTag) self._addCharFormat("replace", SHARED.theme.colRepTag)
self._addCharFormat("hidden", SHARED.theme.colHidden) self._addCharFormat("hidden", SHARED.theme.colHidden)
self._addCharFormat("markup", SHARED.theme.colHidden) self._addCharFormat("markup", SHARED.theme.colHidden)
self._addCharFormat("link", SHARED.theme.colLink, "u")
self._addCharFormat("note", SHARED.theme.colNote) self._addCharFormat("note", SHARED.theme.colNote)
self._addCharFormat("code", SHARED.theme.colCode) self._addCharFormat("code", SHARED.theme.colCode)
self._addCharFormat("keyword", SHARED.theme.colKey) self._addCharFormat("keyword", SHARED.theme.colKey)
self._addCharFormat("tag", SHARED.theme.colTag) self._addCharFormat("tag", SHARED.theme.colTag, "u")
self._addCharFormat("modifier", SHARED.theme.colMod) self._addCharFormat("modifier", SHARED.theme.colMod)
self._addCharFormat("value", SHARED.theme.colVal) self._addCharFormat("value", SHARED.theme.colVal)
self._addCharFormat("optional", SHARED.theme.colOpt) self._addCharFormat("optional", SHARED.theme.colOpt)
@@ -231,6 +233,15 @@ class GuiDocHighlighter(QSyntaxHighlighter):
self._txtRules.append((rxRule, hlRule)) self._txtRules.append((rxRule, hlRule))
self._cmnRules.append((rxRule, hlRule)) self._cmnRules.append((rxRule, hlRule))
# URLs
rxRule = REGEX_PATTERNS.url
hlRule = {
0: self._hStyles["link"],
}
self._minRules.append((rxRule, hlRule))
self._txtRules.append((rxRule, hlRule))
self._cmnRules.append((rxRule, hlRule))
# Alignment Tags # Alignment Tags
rxRule = re.compile(r"(^>{1,2}|<{1,2}$)", re.UNICODE) rxRule = re.compile(r"(^>{1,2}|<{1,2}$)", re.UNICODE)
hlRule = { hlRule = {
@@ -417,8 +428,9 @@ class GuiDocHighlighter(QSyntaxHighlighter):
data = TextBlockData() data = TextBlockData()
self.setCurrentBlockUserData(data) self.setCurrentBlockUserData(data)
data.processText(text, xOff)
if self._spellCheck: if self._spellCheck:
for xPos, xEnd in data.spellCheck(text, xOff): for xPos, xEnd in data.spellCheck():
for x in range(xPos, xEnd): for x in range(xPos, xEnd):
cFmt = self.format(x) cFmt = self.format(x)
cFmt.merge(self._spellErr) cFmt.merge(self._spellErr)
@@ -447,6 +459,8 @@ class GuiDocHighlighter(QSyntaxHighlighter):
charFormat.setFontWeight(QFont.Weight.Bold) charFormat.setFontWeight(QFont.Weight.Bold)
if "i" in styles: if "i" in styles:
charFormat.setFontItalic(True) charFormat.setFontItalic(True)
if "u" in styles:
charFormat.setFontUnderline(True)
if "s" in styles: if "s" in styles:
charFormat.setFontStrikeOut(True) charFormat.setFontStrikeOut(True)
if "err" in styles: if "err" in styles:
@@ -465,22 +479,29 @@ class GuiDocHighlighter(QSyntaxHighlighter):
class TextBlockData(QTextBlockUserData): class TextBlockData(QTextBlockUserData):
__slots__ = ("_spellErrors") __slots__ = ("_text", "_offset", "_metaData", "_spellErrors")
def __init__(self) -> None: def __init__(self) -> None:
super().__init__() super().__init__()
self._spellErrors: list[tuple[int, int]] = [] self._text = ""
self._offset = 0
self._metaData: list[tuple[int, int, str, str]] = []
self._spellErrors: list[tuple[int, int,]] = []
return return
@property
def metaData(self) -> list[tuple[int, int, str, str]]:
"""Return meta data from last check."""
return self._metaData
@property @property
def spellErrors(self) -> list[tuple[int, int]]: def spellErrors(self) -> list[tuple[int, int]]:
"""Return spell error data from last check.""" """Return spell error data from last check."""
return self._spellErrors return self._spellErrors
def spellCheck(self, text: str, offset: int) -> list[tuple[int, int]]: def processText(self, text: str, offset: int) -> None:
"""Run the spell checker and cache the result, and return the """Extract meta data from the text."""
list of spell check errors. self._metaData = []
"""
if "[" in text: if "[" in text:
# Strip shortcodes # Strip shortcodes
for regEx in [RX_FMT_SC, RX_FMT_SV]: for regEx in [RX_FMT_SC, RX_FMT_SV]:
@@ -489,9 +510,25 @@ class TextBlockData(QTextBlockUserData):
pad = " "*(e - s) pad = " "*(e - s)
text = f"{text[:s]}{pad}{text[e:]}" text = f"{text[:s]}{pad}{text[e:]}"
if "http" in text:
# Strip URLs
for res in RX_URL.finditer(text, offset):
if (s := res.start(0)) >= 0 and (e := res.end(0)) >= 0:
pad = " "*(e - s)
text = f"{text[:s]}{pad}{text[e:]}"
self._metaData.append((s, e, res.group(0), "url"))
self._text = text
return
def spellCheck(self) -> list[tuple[int, int]]:
"""Run the spell checker and cache the result, and return the
list of spell check errors.
"""
self._spellErrors = [] self._spellErrors = []
checker = SHARED.spelling checker = SHARED.spelling
for res in RX_WORDS.finditer(text.replace("_", " "), offset): for res in RX_WORDS.finditer(self._text.replace("_", " "), self._offset):
if ( if (
(word := res.group(0)) (word := res.group(0))
and not (word.isnumeric() or word.isupper() or checker.checkWord(word)) and not (word.isnumeric() or word.isupper() or checker.checkWord(word))
+6 -2
View File
@@ -31,7 +31,7 @@ import logging
from enum import Enum from enum import Enum
from PyQt5.QtCore import QPoint, Qt, QUrl, pyqtSignal, pyqtSlot from PyQt5.QtCore import QPoint, Qt, QUrl, pyqtSignal, pyqtSlot
from PyQt5.QtGui import QCursor, QMouseEvent, QPalette, QResizeEvent, QTextCursor from PyQt5.QtGui import QCursor, QDesktopServices, QMouseEvent, QPalette, QResizeEvent, QTextCursor
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QAction, QApplication, QFrame, QHBoxLayout, QMenu, QTextBrowser, QAction, QApplication, QFrame, QHBoxLayout, QMenu, QTextBrowser,
QToolButton, QWidget QToolButton, QWidget
@@ -76,6 +76,7 @@ class GuiDocViewer(QTextBrowser):
# Settings # Settings
self.setMinimumWidth(CONFIG.pxInt(300)) self.setMinimumWidth(CONFIG.pxInt(300))
self.setAutoFillBackground(True) self.setAutoFillBackground(True)
self.setOpenLinks(False)
self.setOpenExternalLinks(False) self.setOpenExternalLinks(False)
self.setFocusPolicy(Qt.FocusPolicy.StrongFocus) self.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
self.setFrameStyle(QFrame.Shape.NoFrame) self.setFrameStyle(QFrame.Shape.NoFrame)
@@ -166,6 +167,7 @@ class GuiDocViewer(QTextBrowser):
self._docTheme.text = SHARED.theme.colText self._docTheme.text = SHARED.theme.colText
self._docTheme.highlight = SHARED.theme.colMark self._docTheme.highlight = SHARED.theme.colMark
self._docTheme.head = SHARED.theme.colHead self._docTheme.head = SHARED.theme.colHead
self._docTheme.link = SHARED.theme.colLink
self._docTheme.comment = SHARED.theme.colHidden self._docTheme.comment = SHARED.theme.colHidden
self._docTheme.note = SHARED.theme.colNote self._docTheme.note = SHARED.theme.colNote
self._docTheme.code = SHARED.theme.colCode self._docTheme.code = SHARED.theme.colCode
@@ -378,8 +380,10 @@ class GuiDocViewer(QTextBrowser):
logger.debug("Clicked link: '%s'", link) logger.debug("Clicked link: '%s'", link)
if (bits := link.partition("_")) and bits[0] == "#tag" and bits[2]: if (bits := link.partition("_")) and bits[0] == "#tag" and bits[2]:
self.loadDocumentTagRequest.emit(bits[2], nwDocMode.VIEW) self.loadDocumentTagRequest.emit(bits[2], nwDocMode.VIEW)
else: elif link.startswith("#"):
self.navigateTo(link) self.navigateTo(link)
elif link.startswith("http"):
QDesktopServices.openUrl(QUrl(url))
return return
@pyqtSlot("QPoint") @pyqtSlot("QPoint")
+15
View File
@@ -95,6 +95,21 @@ class GuiTextDocument(QTextDocument):
return return
def metaDataAtPos(self, pos: int) -> tuple[str, str]:
"""Check if there is meta data available at a given position in
the document, and if so, return it.
"""
cursor = QTextCursor(self)
cursor.setPosition(pos)
block = cursor.block()
data = block.userData()
if block.isValid() and isinstance(data, TextBlockData):
if (check := pos - block.position()) >= 0:
for cPos, cEnd, cData, cType in data.metaData:
if cPos <= check <= cEnd:
return cData, cType
return "", ""
def spellErrorAtPos(self, pos: int) -> tuple[str, int, int, list[str]]: def spellErrorAtPos(self, pos: int) -> tuple[str, int, int, list[str]]:
"""Check if there is a misspelled word at a given position in """Check if there is a misspelled word at a given position in
the document, and if so, return it. the document, and if so, return it.
+6
View File
@@ -32,6 +32,7 @@ from novelwriter.constants import nwRegEx
class RegExPatterns: class RegExPatterns:
# Static RegExes # Static RegExes
_rxUrl = re.compile(nwRegEx.URL, re.ASCII)
_rxWords = re.compile(nwRegEx.WORDS, re.UNICODE) _rxWords = re.compile(nwRegEx.WORDS, re.UNICODE)
_rxBreak = re.compile(nwRegEx.BREAK, re.UNICODE) _rxBreak = re.compile(nwRegEx.BREAK, re.UNICODE)
_rxItalic = re.compile(nwRegEx.FMT_EI, re.UNICODE) _rxItalic = re.compile(nwRegEx.FMT_EI, re.UNICODE)
@@ -40,6 +41,11 @@ class RegExPatterns:
_rxSCPlain = re.compile(nwRegEx.FMT_SC, re.UNICODE) _rxSCPlain = re.compile(nwRegEx.FMT_SC, re.UNICODE)
_rxSCValue = re.compile(nwRegEx.FMT_SV, re.UNICODE) _rxSCValue = re.compile(nwRegEx.FMT_SV, re.UNICODE)
@property
def url(self) -> re.Pattern:
"""Find URLs."""
return self._rxUrl
@property @property
def wordSplit(self) -> re.Pattern: def wordSplit(self) -> re.Pattern:
"""Split text into words.""" """Split text into words."""
+19 -1
View File
@@ -29,7 +29,10 @@ from time import time
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from PyQt5.QtCore import Qt, QTimer, QUrl, pyqtSignal, pyqtSlot from PyQt5.QtCore import Qt, QTimer, QUrl, pyqtSignal, pyqtSlot
from PyQt5.QtGui import QCloseEvent, QColor, QCursor, QFont, QPalette, QResizeEvent, QTextDocument from PyQt5.QtGui import (
QCloseEvent, QColor, QCursor, QDesktopServices, QFont, QPalette,
QResizeEvent, QTextDocument
)
from PyQt5.QtPrintSupport import QPrinter, QPrintPreviewDialog from PyQt5.QtPrintSupport import QPrinter, QPrintPreviewDialog
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QAbstractItemView, QApplication, QFormLayout, QGridLayout, QHBoxLayout, QAbstractItemView, QApplication, QFormLayout, QGridLayout, QHBoxLayout,
@@ -711,6 +714,7 @@ class _PreviewWidget(QTextBrowser):
self.setMinimumWidth(40*SHARED.theme.textNWidth) self.setMinimumWidth(40*SHARED.theme.textNWidth)
self.setTabStopDistance(CONFIG.getTabWidth()) self.setTabStopDistance(CONFIG.getTabWidth())
self.setOpenExternalLinks(False) self.setOpenExternalLinks(False)
self.setOpenLinks(False)
self.document().setDocumentMargin(CONFIG.getTextMargin()) self.document().setDocumentMargin(CONFIG.getTextMargin())
self.setPlaceholderText(self.tr( self.setPlaceholderText(self.tr(
@@ -719,6 +723,9 @@ class _PreviewWidget(QTextBrowser):
self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOn) self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOn)
# Signals
self.anchorClicked.connect(self._linkClicked)
# Document Age # Document Age
aPalette = self.palette() aPalette = self.palette()
aPalette.setColor(QPalette.ColorRole.Window, aPalette.toolTipBase().color()) aPalette.setColor(QPalette.ColorRole.Window, aPalette.toolTipBase().color())
@@ -855,6 +862,17 @@ class _PreviewWidget(QTextBrowser):
# Private Slots # Private Slots
## ##
@pyqtSlot("QUrl")
def _linkClicked(self, url: QUrl) -> None:
"""Process a clicked link in the document."""
if link := url.url():
logger.debug("Clicked link: '%s'", link)
if link.startswith("#"):
self.navigateTo(link.lstrip("#"))
elif link.startswith("http"):
QDesktopServices.openUrl(QUrl(url))
return
@pyqtSlot() @pyqtSlot()
def _updateBuildAge(self) -> None: def _updateBuildAge(self) -> None:
"""Update the build time and the fuzzy age.""" """Update the build time and the fuzzy age."""
+5 -3
View File
@@ -1,14 +1,14 @@
%%~name: Making a Scene %%~name: Making a Scene
%%~path: 6a2d6d5f4f401/636b6aa9b697b %%~path: 6a2d6d5f4f401/636b6aa9b697b
%%~kind: NOVEL/DOCUMENT %%~kind: NOVEL/DOCUMENT
%%~hash: 349d9ce3d59ad241d63b01380c53a7fb26ce9f19 %%~hash: 8d245fa740926779d19741ff7f75ef387b55130c
%%~date: Unknown/2024-10-24 23:44:27 %%~date: Unknown/2024-10-25 23:54:52
### Making a Scene ### Making a Scene
@pov: Jane @pov: Jane
@char: John, Jane @char: John, Jane
@location: Earth @location: Earth
@mention: Bob, Space @mention: Space
A scene is defined by a level three heading, like the one at the top of this page. The scene will be assigned to the chapter preceding it in the project tree. The scene document can be sorted after the chapter document, or as a child of the chapter. Both result in the same output in the end, so it is a matter of preference. A scene is defined by a level three heading, like the one at the top of this page. The scene will be assigned to the chapter preceding it in the project tree. The scene document can be sorted after the chapter document, or as a child of the chapter. Both result in the same output in the end, so it is a matter of preference.
@@ -20,6 +20,8 @@ In addition, the editor supports automatic formatting of “quotes”, both doub
If you have the need for it, you can also add text that can be automatically replaced by other text when you generate a preview or export the project. Now, lets auto-replace this A with <A>, and this C with <C>. While <E> is just <E>. Press Ctrl+R to see what this looks like in the view pane. The list of auto-replaced text is set in Project Settings. If you have the need for it, you can also add text that can be automatically replaced by other text when you generate a preview or export the project. Now, lets auto-replace this A with <A>, and this C with <C>. While <E> is just <E>. Press Ctrl+R to see what this looks like in the view pane. The list of auto-replaced text is set in Project Settings.
You can also add URLs like http://www.example.com to the text.
The editor also supports non breaking spaces, and the spell checker accepts long dashes—like this—as valid word separators. Regular dashes are also supported and can be automatically inserted when typing two hyphens. The editor also supports non breaking spaces, and the spell checker accepts long dashes—like this—as valid word separators. Regular dashes are also supported and can be automatically inserted when typing two hyphens.
Thin spaces and thin non-breaking spaces are also supported from the Insert menu, and can be used to separate numbers from their units, like: 25kg.[footnote:f4xr5] Thin spaces and thin non-breaking spaces are also supported from the Insert menu, and can be used to separate numbers from their units, like: 25kg.[footnote:f4xr5]
+6 -6
View File
@@ -1,6 +1,6 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.6a2" hexVersion="0x020600a2" fileVersion="1.5" fileRevision="4" timeStamp="2024-10-24 23:58:23"> <novelWriterXML appVersion="2.6a2" hexVersion="0x020600a2" fileVersion="1.5" fileRevision="4" timeStamp="2024-10-26 16:59:09">
<project id="e2be99af-f9bf-4403-857a-c3d1ac25abea" saveCount="2088" autoCount="280" editTime="93793"> <project id="e2be99af-f9bf-4403-857a-c3d1ac25abea" saveCount="2132" autoCount="279" editTime="95062">
<name>Sample Project</name> <name>Sample Project</name>
<author>Jane Smith</author> <author>Jane Smith</author>
</project> </project>
@@ -36,7 +36,7 @@
<entry key="i56be10" count="1" red="220" green="138" blue="221" shape="BLOCK_4">Main</entry> <entry key="i56be10" count="1" red="220" green="138" blue="221" shape="BLOCK_4">Main</entry>
</importance> </importance>
</settings> </settings>
<content items="31" novelWords="1004" notesWords="416"> <content items="31" novelWords="1014" notesWords="416">
<item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL"> <item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL">
<meta expanded="yes" /> <meta expanded="yes" />
<name status="sc24b8f" import="ia857f0">Novel</name> <name status="sc24b8f" import="ia857f0">Novel</name>
@@ -58,11 +58,11 @@
<name status="sf24ce6" import="ia857f0" active="yes">Chapter One</name> <name status="sf24ce6" import="ia857f0" active="yes">Chapter One</name>
</item> </item>
<item handle="636b6aa9b697b" parent="6a2d6d5f4f401" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="636b6aa9b697b" parent="6a2d6d5f4f401" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="no" heading="H3" charCount="2937" wordCount="520" paraCount="15" cursorPos="19" /> <meta expanded="no" heading="H3" charCount="2999" wordCount="530" paraCount="16" cursorPos="357" />
<name status="s90e6c9" import="ia857f0" active="yes">Making a Scene</name> <name status="s90e6c9" import="ia857f0" active="yes">Making a Scene</name>
</item> </item>
<item handle="bc0cbd2a407f3" parent="6a2d6d5f4f401" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="bc0cbd2a407f3" parent="6a2d6d5f4f401" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="no" heading="H3" charCount="548" wordCount="108" paraCount="3" cursorPos="650" /> <meta expanded="no" heading="H3" charCount="548" wordCount="108" paraCount="3" cursorPos="691" />
<name status="s90e6c9" import="ia857f0" active="yes">Another Scene</name> <name status="s90e6c9" import="ia857f0" active="yes">Another Scene</name>
</item> </item>
<item handle="ba8a28a246524" parent="7031beac91f75" root="7031beac91f75" order="4" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="ba8a28a246524" parent="7031beac91f75" root="7031beac91f75" order="4" type="FILE" class="NOVEL" layout="DOCUMENT">
@@ -102,7 +102,7 @@
<name status="sf12341" import="ia857f0">Main Characters</name> <name status="sf12341" import="ia857f0">Main Characters</name>
</item> </item>
<item handle="14298de4d9524" parent="f7e2d9f330615" root="f6622b4617424" order="0" type="FILE" class="CHARACTER" layout="NOTE"> <item handle="14298de4d9524" parent="f7e2d9f330615" root="f6622b4617424" order="0" type="FILE" class="CHARACTER" layout="NOTE">
<meta expanded="no" heading="H1" charCount="49" wordCount="9" paraCount="1" cursorPos="15" /> <meta expanded="no" heading="H1" charCount="49" wordCount="9" paraCount="1" cursorPos="23" />
<name status="sf12341" import="i2d7a54" active="yes">John Smith</name> <name status="sf12341" import="i2d7a54" active="yes">John Smith</name>
</item> </item>
<item handle="bb2c23b3c42cc" parent="f7e2d9f330615" root="f6622b4617424" order="1" type="FILE" class="CHARACTER" layout="NOTE"> <item handle="bb2c23b3c42cc" parent="f7e2d9f330615" root="f6622b4617424" order="1" type="FILE" class="CHARACTER" layout="NOTE">
+4 -2
View File
@@ -1,12 +1,14 @@
%%~name: Prologue %%~name: Prologue
%%~path: b3643d0f92e32/88d59a277361b %%~path: b3643d0f92e32/88d59a277361b
%%~kind: NOVEL/DOCUMENT %%~kind: NOVEL/DOCUMENT
%%~hash: 605a96ba35297cd7d49b753b3bda9a20b9b29d93 %%~hash: 377a72ba340ff458a00d98af745e9fa0c79fb26c
%%~date: Unknown/2024-04-27 16:40:18 %%~date: Unknown/2024-10-25 19:16:49
##! Prologue ##! Prologue
% Synopsis: Explanation from the lipsum.com website. % Synopsis: Explanation from the lipsum.com website.
_Lorem Ipsum_ is simply dummy text[footnote:f9kgf] of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum. _Lorem Ipsum_ is simply dummy text[footnote:f9kgf] of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
See http://lipsum.com
%Footnote.f9kgf: _Lorem ipsum_ is typically a corrupted version of De finibus bonorum et malorum, a 1st-century BC text by the Roman statesman and philosopher Cicero, with words altered, added, and removed to make it nonsensical and improper Latin. (Source: Wikipedia) %Footnote.f9kgf: _Lorem ipsum_ is typically a corrupted version of De finibus bonorum et malorum, a 1st-century BC text by the Roman statesman and philosopher Cicero, with words altered, added, and removed to make it nonsensical and improper Latin. (Source: Wikipedia)
+4 -4
View File
@@ -1,6 +1,6 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.5a2" hexVersion="0x020500a2" fileVersion="1.5" fileRevision="4" timeStamp="2024-04-27 16:40:24"> <novelWriterXML appVersion="2.6a2" hexVersion="0x020600a2" fileVersion="1.5" fileRevision="4" timeStamp="2024-10-25 19:16:56">
<project id="5ac0df12-8b8b-476b-9905-21d33685687c" saveCount="45" autoCount="26" editTime="2168"> <project id="5ac0df12-8b8b-476b-9905-21d33685687c" saveCount="48" autoCount="28" editTime="2374">
<name>Lorem Ipsum</name> <name>Lorem Ipsum</name>
<author>lipsum.com</author> <author>lipsum.com</author>
</project> </project>
@@ -31,7 +31,7 @@
<entry key="id6b1d0" count="0" red="50" green="200" blue="0" shape="SQUARE">Main</entry> <entry key="id6b1d0" count="0" red="50" green="200" blue="0" shape="SQUARE">Main</entry>
</importance> </importance>
</settings> </settings>
<content items="21" novelWords="3109" notesWords="738"> <content items="21" novelWords="3111" notesWords="738">
<item handle="b3643d0f92e32" parent="None" root="b3643d0f92e32" order="0" type="ROOT" class="NOVEL"> <item handle="b3643d0f92e32" parent="None" root="b3643d0f92e32" order="0" type="ROOT" class="NOVEL">
<meta expanded="yes" /> <meta expanded="yes" />
<name status="sbaa94f" import="i613591">Novel</name> <name status="sbaa94f" import="i613591">Novel</name>
@@ -45,7 +45,7 @@
<name status="sedd043" import="i613591" active="yes">Front Matter</name> <name status="sedd043" import="i613591" active="yes">Front Matter</name>
</item> </item>
<item handle="88d59a277361b" parent="b3643d0f92e32" root="b3643d0f92e32" order="2" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="88d59a277361b" parent="b3643d0f92e32" root="b3643d0f92e32" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="no" heading="H2" charCount="600" wordCount="92" paraCount="1" cursorPos="931" /> <meta expanded="no" heading="H2" charCount="605" wordCount="94" paraCount="2" cursorPos="47" />
<name status="s92a87b" import="i613591" active="yes">Prologue</name> <name status="s92a87b" import="i613591" active="yes">Prologue</name>
</item> </item>
<item handle="db7e733775d4d" parent="b3643d0f92e32" root="b3643d0f92e32" order="3" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="db7e733775d4d" parent="b3643d0f92e32" root="b3643d0f92e32" order="3" type="FILE" class="NOVEL" layout="DOCUMENT">
@@ -17,7 +17,7 @@
}, },
"88d59a277361b": { "88d59a277361b": {
"headings": { "headings": {
"T0001": {"level": "H2", "title": "Prologue", "line": 1, "tag": "", "cCount": 584, "wCount": 92, "pCount": 1, "synopsis": "Explanation from the lipsum.com website."} "T0001": {"level": "H2", "title": "Prologue", "line": 1, "tag": "", "cCount": 605, "wCount": 94, "pCount": 2, "synopsis": "Explanation from the lipsum.com website."}
}, },
"notes": { "notes": {
"footnotes": ["f9kgf"] "footnotes": ["f9kgf"]
@@ -7,6 +7,7 @@
<ns0:Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml" /> <ns0:Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml" />
<ns0:Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml" /> <ns0:Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml" />
<ns0:Override PartName="/word/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml" /> <ns0:Override PartName="/word/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml" />
<ns0:Override PartName="/word/fontTable.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.fontTable+xml" />
<ns0:Override PartName="/word/header1.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml" /> <ns0:Override PartName="/word/header1.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml" />
<ns0:Override PartName="/word/header2.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml" /> <ns0:Override PartName="/word/header2.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml" />
<ns0:Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml" /> <ns0:Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml" />
@@ -1,9 +1,9 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<ns0:Properties xmlns:ns0="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties"> <ns0:Properties xmlns:ns0="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties">
<ns0:TotalTime>36</ns0:TotalTime> <ns0:TotalTime>39</ns0:TotalTime>
<ns0:Application>novelWriter/2.6a1</ns0:Application> <ns0:Application>novelWriter/2.6a2</ns0:Application>
<ns0:Words>4029</ns0:Words> <ns0:Words>4031</ns0:Words>
<ns0:Characters>21251</ns0:Characters> <ns0:Characters>21271</ns0:Characters>
<ns0:CharactersWithSpaces>24914</ns0:CharactersWithSpaces> <ns0:CharactersWithSpaces>24935</ns0:CharactersWithSpaces>
<ns0:Paragraphs>42</ns0:Paragraphs> <ns0:Paragraphs>43</ns0:Paragraphs>
</ns0:Properties> </ns0:Properties>
@@ -1,11 +1,10 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<dcterms:created xsi:type="dcterms:W3CDTF">2024-10-21T13:54:49</dcterms:created> <dcterms:created xsi:type="dcterms:W3CDTF">2024-10-26T17:01:14</dcterms:created>
<dcterms:modified xsi:type="dcterms:W3CDTF">2024-10-21T13:54:49</dcterms:modified> <dcterms:modified xsi:type="dcterms:W3CDTF">2024-10-26T17:01:14</dcterms:modified>
<dc:creator>lipsum.com</dc:creator> <dc:creator>lipsum.com</dc:creator>
<dc:title>Lorem Ipsum</dc:title> <dc:title>Lorem Ipsum</dc:title>
<dc:creator>lipsum.com</dc:creator> <dc:language>en-GB</dc:language>
<dc:language>en_GB</dc:language> <cp:revision>48</cp:revision>
<cp:revision>45</cp:revision>
<cp:lastModifiedBy>lipsum.com</cp:lastModifiedBy> <cp:lastModifiedBy>lipsum.com</cp:lastModifiedBy>
</coreProperties> </cp:coreProperties>
@@ -146,6 +146,24 @@
<w:rPr /> <w:rPr />
<w:t xml:space="preserve"> of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</w:t> <w:t xml:space="preserve"> of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</w:t>
</w:r> </w:r>
</w:p>
<w:p>
<w:pPr>
<w:pStyle w:val="Normal" />
<w:ind w:firstLine="335" />
</w:pPr>
<w:r>
<w:rPr />
<w:t xml:space="preserve">See </w:t>
</w:r>
<w:hyperlink r:id="rId1">
<w:r>
<w:rPr>
<w:rStyle w:val="InternetLink" />
</w:rPr>
<w:t>http://lipsum.com</w:t>
</w:r>
</w:hyperlink>
<w:r> <w:r>
<w:br w:type="page" /> <w:br w:type="page" />
</w:r> </w:r>
@@ -1380,8 +1398,8 @@
</w:r> </w:r>
</w:p> </w:p>
<w:sectPr> <w:sectPr>
<w:headerReference w:type="first" r:id="rId5" /> <w:headerReference w:type="first" r:id="rId7" />
<w:headerReference w:type="default" r:id="rId4" /> <w:headerReference w:type="default" r:id="rId6" />
<w:footnotePr> <w:footnotePr>
<w:numFmt w:val="decimal" /> <w:numFmt w:val="decimal" />
</w:footnotePr> </w:footnotePr>
@@ -1,8 +1,10 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<ns0:Relationships xmlns:ns0="http://schemas.openxmlformats.org/package/2006/relationships"> <ns0:Relationships xmlns:ns0="http://schemas.openxmlformats.org/package/2006/relationships">
<ns0:Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml" /> <ns0:Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink" Target="http://lipsum.com" TargetMode="External" />
<ns0:Relationship Id="rId4" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/header" Target="header1.xml" /> <ns0:Relationship Id="rId4" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml" />
<ns0:Relationship Id="rId5" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/header" Target="header2.xml" /> <ns0:Relationship Id="rId5" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/fontTable" Target="fontTable.xml" />
<ns0:Relationship Id="rId7" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings" Target="settings.xml" /> <ns0:Relationship Id="rId6" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/header" Target="header1.xml" />
<ns0:Relationship Id="rId8" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/footnotes" Target="footnotes.xml" /> <ns0:Relationship Id="rId7" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/header" Target="header2.xml" />
<ns0:Relationship Id="rId9" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings" Target="settings.xml" />
<ns0:Relationship Id="rId10" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/footnotes" Target="footnotes.xml" />
</ns0:Relationships> </ns0:Relationships>
@@ -0,0 +1,6 @@
<?xml version='1.0' encoding='utf-8'?>
<w:fonts xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
<w:font w:name="Source Sans Pro">
<w:pitch w:val="variable" />
</w:font>
</w:fonts>
@@ -5,6 +5,12 @@
<w:pPr> <w:pPr>
<w:pStyle w:val="FootnoteText" /> <w:pStyle w:val="FootnoteText" />
</w:pPr> </w:pPr>
<w:r>
<w:rPr>
<w:vertAlign w:val="superscript" />
</w:rPr>
<w:footnoteRef />
</w:r>
<w:r> <w:r>
<w:rPr> <w:rPr>
<w:i /> <w:i />
@@ -7,31 +7,24 @@
<w:rPr /> <w:rPr />
</w:pPr> </w:pPr>
<w:r> <w:r>
<w:rPr />
<w:t xml:space="preserve">Page </w:t> <w:t xml:space="preserve">Page </w:t>
</w:r> </w:r>
<w:r> <w:r>
<w:rPr />
<w:fldChar w:fldCharType="begin" /> <w:fldChar w:fldCharType="begin" />
</w:r> </w:r>
<w:r> <w:r>
<w:rPr />
<w:t xml:space="preserve"> PAGE </w:t> <w:t xml:space="preserve"> PAGE </w:t>
</w:r> </w:r>
<w:r> <w:r>
<w:rPr />
<w:fldChar w:fldCharType="separate" /> <w:fldChar w:fldCharType="separate" />
</w:r> </w:r>
<w:r> <w:r>
<w:rPr /> <w:t>0</w:t>
<w:t xml:space="preserve">2</w:t>
</w:r> </w:r>
<w:r> <w:r>
<w:rPr />
<w:fldChar w:fldCharType="end" /> <w:fldChar w:fldCharType="end" />
</w:r> </w:r>
<w:r> <w:r>
<w:rPr />
<w:t xml:space="preserve"> - Lorem Ipsum (lipsum.com)</w:t> <w:t xml:space="preserve"> - Lorem Ipsum (lipsum.com)</w:t>
</w:r> </w:r>
</w:p> </w:p>
+3 -3
View File
@@ -1,6 +1,6 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<ns0:Relationships xmlns:ns0="http://schemas.openxmlformats.org/package/2006/relationships"> <ns0:Relationships xmlns:ns0="http://schemas.openxmlformats.org/package/2006/relationships">
<ns0:Relationship Id="rId1" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml" /> <ns0:Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml" />
<ns0:Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml" /> <ns0:Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml" />
<ns0:Relationship Id="rId6" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml" /> <ns0:Relationship Id="rId8" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml" />
</ns0:Relationships> </ns0:Relationships>
@@ -3,17 +3,20 @@
<w:footnotePr> <w:footnotePr>
<w:numFmt w:val="decimal" /> <w:numFmt w:val="decimal" />
</w:footnotePr> </w:footnotePr>
<w:compat>
<w:compatSetting w:name="compatibilityMode" w:uri="http://schemas.microsoft.com/office/word" w:val="12" />
</w:compat>
<w:docVars> <w:docVars>
<w:docVar w:name="ManuscriptTitleCount" w:val="11" /> <w:docVar w:name="ManuscriptTitleCount" w:val="11" />
<w:docVar w:name="ManuscriptParagraphCount" w:val="42" /> <w:docVar w:name="ManuscriptParagraphCount" w:val="43" />
<w:docVar w:name="ManuscriptAllWords" w:val="4029" /> <w:docVar w:name="ManuscriptAllWords" w:val="4031" />
<w:docVar w:name="ManuscriptTextWords" w:val="3705" /> <w:docVar w:name="ManuscriptTextWords" w:val="3707" />
<w:docVar w:name="ManuscriptTitleWords" w:val="21" /> <w:docVar w:name="ManuscriptTitleWords" w:val="21" />
<w:docVar w:name="ManuscriptAllChars" w:val="27014" /> <w:docVar w:name="ManuscriptAllChars" w:val="27035" />
<w:docVar w:name="ManuscriptTextChars" w:val="24914" /> <w:docVar w:name="ManuscriptTextChars" w:val="24935" />
<w:docVar w:name="ManuscriptTitleChars" w:val="123" /> <w:docVar w:name="ManuscriptTitleChars" w:val="123" />
<w:docVar w:name="ManuscriptAllWordChars" w:val="23075" /> <w:docVar w:name="ManuscriptAllWordChars" w:val="23095" />
<w:docVar w:name="ManuscriptTextWordChars" w:val="21251" /> <w:docVar w:name="ManuscriptTextWordChars" w:val="21271" />
<w:docVar w:name="ManuscriptTitleWordChars" w:val="113" /> <w:docVar w:name="ManuscriptTitleWordChars" w:val="113" />
</w:docVars> </w:docVars>
</w:settings> </w:settings>
@@ -6,7 +6,7 @@
<w:rFonts w:ascii="Source Sans Pro" w:hAnsi="Source Sans Pro" w:cs="Source Sans Pro" /> <w:rFonts w:ascii="Source Sans Pro" w:hAnsi="Source Sans Pro" w:cs="Source Sans Pro" />
<w:sz w:val="24" /> <w:sz w:val="24" />
<w:szCs w:val="24" /> <w:szCs w:val="24" />
<w:lang w:val="en_GB" /> <w:lang w:val="en-GB" />
</w:rPr> </w:rPr>
</w:rPrDefault> </w:rPrDefault>
<w:pPrDefault> <w:pPrDefault>
@@ -142,11 +142,18 @@
<w:basedOn w:val="Normal" /> <w:basedOn w:val="Normal" />
<w:pPr> <w:pPr>
<w:spacing w:before="0" w:after="90" w:line="220" /> <w:spacing w:before="0" w:after="90" w:line="220" />
<w:ind w:left="272" w:hanging="272" /> <w:ind w:left="272" />
</w:pPr> </w:pPr>
<w:rPr> <w:rPr>
<w:sz w:val="19" /> <w:sz w:val="19" />
<w:szCs w:val="19" /> <w:szCs w:val="19" />
</w:rPr> </w:rPr>
</w:style> </w:style>
<w:style w:type="character" w:styleId="InternetLink">
<w:name w:val="Hyperlink" />
<w:rPr>
<w:color w:val="4271ae" />
<w:u w:val="single" />
</w:rPr>
</w:style>
</w:styles> </w:styles>
@@ -1,6 +1,6 @@
"Title","Document","Words","Pars","POV","Characters","Plot","Locations","Synopsis" "Title","Document","Words","Pars","POV","Characters","Plot","Locations","Synopsis"
"Lorem Ipsum","Lorem Ipsum","40","3","","","","","" "Lorem Ipsum","Lorem Ipsum","40","3","","","","",""
"Prologue","Prologue","92","1","","","","","Explanation from the lipsum.com website." "Prologue","Prologue","94","2","","","","","Explanation from the lipsum.com website."
"Act One","Act One","6","1","","","","","" "Act One","Act One","6","1","","","","",""
"Chapter One","Chapter One","67","1","Bod","","Main","Europe","Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque at aliquam quam." "Chapter One","Chapter One","67","1","Bod","","Main","Europe","Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque at aliquam quam."
"Scene One","Scene One","174","2","Bod","","Main","Europe","Aenean ut placerat velit. Etiam laoreet ullamcorper risus, eget lobortis enim scelerisque non. Suspendisse id maximus nunc, et mollis sapien. Curabitur vel semper sapien, non pulvinar dolor. Etiam finibus nisi vel mi molestie consectetur." "Scene One","Scene One","174","2","Bod","","Main","Europe","Aenean ut placerat velit. Etiam laoreet ullamcorper risus, eget lobortis enim scelerisque non. Suspendisse id maximus nunc, et mollis sapien. Curabitur vel semper sapien, non pulvinar dolor. Etiam finibus nisi vel mi molestie consectetur."
1 Title Document Words Pars POV Characters Plot Locations Synopsis
2 Lorem Ipsum Lorem Ipsum 40 3
3 Prologue Prologue 92 94 1 2 Explanation from the lipsum.com website.
4 Act One Act One 6 1
5 Chapter One Chapter One 67 1 Bod Main Europe Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque at aliquam quam.
6 Scene One Scene One 174 2 Bod Main Europe Aenean ut placerat velit. Etiam laoreet ullamcorper risus, eget lobortis enim scelerisque non. Suspendisse id maximus nunc, et mollis sapien. Curabitur vel semper sapien, non pulvinar dolor. Etiam finibus nisi vel mi molestie consectetur.
@@ -18,6 +18,8 @@ The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for t
_Lorem Ipsum_ is simply dummy text[1] of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum. _Lorem Ipsum_ is simply dummy text[1] of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
See http://lipsum.com
# Part: Act One # Part: Act One
“Fusce maximus felis libero” “Fusce maximus felis libero”
@@ -29,6 +29,7 @@ h4 {margin-top: 1.53em; margin-bottom: 0.65em;}
<h1 style='page-break-before: always;'>Prologue</h1> <h1 style='page-break-before: always;'>Prologue</h1>
<p class='comment' style='text-align: justify;'><strong><span style='color: #813709'>Synopsis:</span></strong> <span style='color: #813709'>Explanation from the lipsum.com website.</span></p> <p class='comment' style='text-align: justify;'><strong><span style='color: #813709'>Synopsis:</span></strong> <span style='color: #813709'>Explanation from the lipsum.com website.</span></p>
<p style='text-align: justify;'><em>Lorem Ipsum</em> is simply dummy text<sup><a href='#footnote_1'>1</a></sup> of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</p> <p style='text-align: justify;'><em>Lorem Ipsum</em> is simply dummy text<sup><a href='#footnote_1'>1</a></sup> of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</p>
<p style='text-align: justify; text-indent: 1.40em;'>See <a href='http://lipsum.com'>http://lipsum.com</a></p>
<h1 class='title' style='text-align: center; page-break-before: always;'>Part: Act One</h1> <h1 class='title' style='text-align: center; page-break-before: always;'>Part: Act One</h1>
<p style='text-align: center;'>“Fusce maximus felis libero”</p> <p style='text-align: center;'>“Fusce maximus felis libero”</p>
<h1 style='page-break-before: always;'>Chapter: Chapter One</h1> <h1 style='page-break-before: always;'>Chapter: Chapter One</h1>
@@ -2,8 +2,8 @@
"meta": { "meta": {
"projectName": "Lorem Ipsum", "projectName": "Lorem Ipsum",
"novelAuthor": "lipsum.com", "novelAuthor": "lipsum.com",
"buildTime": 1729676162, "buildTime": 1729877010,
"buildTimeStr": "2024-10-23 11:36:02" "buildTimeStr": "2024-10-25 19:23:30"
}, },
"text": { "text": {
"css": [ "css": [
@@ -34,7 +34,8 @@
[ [
"<h1 style='page-break-before: always;'>Prologue</h1>", "<h1 style='page-break-before: always;'>Prologue</h1>",
"<p class='comment' style='text-align: justify;'><strong><span style='color: #813709'>Synopsis:</span></strong> <span style='color: #813709'>Explanation from the lipsum.com website.</span></p>", "<p class='comment' style='text-align: justify;'><strong><span style='color: #813709'>Synopsis:</span></strong> <span style='color: #813709'>Explanation from the lipsum.com website.</span></p>",
"<p style='text-align: justify;'><em>Lorem Ipsum</em> is simply dummy text<sup><a href='#footnote_1'>1</a></sup> of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</p>" "<p style='text-align: justify;'><em>Lorem Ipsum</em> is simply dummy text<sup><a href='#footnote_1'>1</a></sup> of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</p>",
"<p style='text-align: justify; text-indent: 1.40em;'>See <a href='http://lipsum.com'>http://lipsum.com</a></p>"
], ],
[ [
"<h1 class='title' style='text-align: center; page-break-before: always;'>Part: Act One</h1>", "<h1 class='title' style='text-align: center; page-break-before: always;'>Part: Act One</h1>",
@@ -2,8 +2,8 @@
"meta": { "meta": {
"projectName": "Lorem Ipsum", "projectName": "Lorem Ipsum",
"novelAuthor": "lipsum.com", "novelAuthor": "lipsum.com",
"buildTime": 1729725334, "buildTime": 1729877010,
"buildTimeStr": "2024-10-24 01:15:34" "buildTimeStr": "2024-10-25 19:23:30"
}, },
"text": { "text": {
"nwd": [ "nwd": [
@@ -31,6 +31,8 @@
"", "",
"_Lorem Ipsum_ is simply dummy text[footnote:f9kgf] of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.", "_Lorem Ipsum_ is simply dummy text[footnote:f9kgf] of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.",
"", "",
"See http://lipsum.com",
"",
"%Footnote.f9kgf: _Lorem ipsum_ is typically a corrupted version of De finibus bonorum et malorum, a 1st-century BC text by the Roman statesman and philosopher Cicero, with words altered, added, and removed to make it nonsensical and improper Latin. (Source: Wikipedia)" "%Footnote.f9kgf: _Lorem ipsum_ is typically a corrupted version of De finibus bonorum et malorum, a 1st-century BC text by the Roman statesman and philosopher Cicero, with words altered, added, and removed to make it nonsensical and improper Latin. (Source: Wikipedia)"
], ],
[ [
@@ -19,6 +19,8 @@ The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for t
_Lorem Ipsum_ is simply dummy text[footnote:f9kgf] of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum. _Lorem Ipsum_ is simply dummy text[footnote:f9kgf] of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
See http://lipsum.com
%Footnote.f9kgf: _Lorem ipsum_ is typically a corrupted version of De finibus bonorum et malorum, a 1st-century BC text by the Roman statesman and philosopher Cicero, with words altered, added, and removed to make it nonsensical and improper Latin. (Source: Wikipedia) %Footnote.f9kgf: _Lorem ipsum_ is typically a corrupted version of De finibus bonorum et malorum, a 1st-century BC text by the Roman statesman and philosopher Cicero, with words altered, added, and removed to make it nonsensical and improper Latin. (Source: Wikipedia)
# Act One # Act One
@@ -1,13 +1,13 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<office:document xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text"> <office:document xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text">
<office:meta> <office:meta>
<meta:creation-date>2024-10-22T21:00:03</meta:creation-date> <meta:creation-date>2024-10-25T19:19:43</meta:creation-date>
<meta:generator>novelWriter/2.6a1</meta:generator> <meta:generator>novelWriter/2.6a2</meta:generator>
<meta:initial-creator>lipsum.com</meta:initial-creator> <meta:initial-creator>lipsum.com</meta:initial-creator>
<meta:editing-cycles>45</meta:editing-cycles> <meta:editing-cycles>48</meta:editing-cycles>
<meta:editing-duration>P0DT0H36M8S</meta:editing-duration> <meta:editing-duration>P0DT0H39M34S</meta:editing-duration>
<dc:title>Lorem Ipsum</dc:title> <dc:title>Lorem Ipsum</dc:title>
<dc:date>2024-10-22T21:00:03</dc:date> <dc:date>2024-10-25T19:19:43</dc:date>
<dc:creator>lipsum.com</dc:creator> <dc:creator>lipsum.com</dc:creator>
</office:meta> </office:meta>
<office:font-face-decls> <office:font-face-decls>
@@ -125,14 +125,17 @@
<style:text-properties fo:font-style="italic" /> <style:text-properties fo:font-style="italic" />
</style:style> </style:style>
<style:style style:name="T7" style:family="text"> <style:style style:name="T7" style:family="text">
<style:text-properties fo:font-weight="bold" fo:color="#f5871f" /> <style:text-properties fo:color="#4271ae" style:text-underline-style="solid" style:text-underline-width="auto" style:text-underline-color="font-color" />
</style:style> </style:style>
<style:style style:name="T8" style:family="text"> <style:style style:name="T8" style:family="text">
<style:text-properties fo:color="#4271ae" /> <style:text-properties fo:font-weight="bold" fo:color="#f5871f" />
</style:style> </style:style>
<style:style style:name="T9" style:family="text"> <style:style style:name="T9" style:family="text">
<style:text-properties fo:color="#4271ae" /> <style:text-properties fo:color="#4271ae" />
</style:style> </style:style>
<style:style style:name="T10" style:family="text">
<style:text-properties fo:color="#4271ae" />
</style:style>
</office:automatic-styles> </office:automatic-styles>
<office:master-styles> <office:master-styles>
<style:master-page style:name="Standard" style:page-layout-name="PM1"> <style:master-page style:name="Standard" style:page-layout-name="PM1">
@@ -148,15 +151,15 @@
<office:text> <office:text>
<text:user-field-decls> <text:user-field-decls>
<text:user-field-decl office:value-type="float" office:value="19" text:name="ManuscriptTitleCount" /> <text:user-field-decl office:value-type="float" office:value="19" text:name="ManuscriptTitleCount" />
<text:user-field-decl office:value-type="float" office:value="45" text:name="ManuscriptParagraphCount" /> <text:user-field-decl office:value-type="float" office:value="46" text:name="ManuscriptParagraphCount" />
<text:user-field-decl office:value-type="float" office:value="4163" text:name="ManuscriptAllWords" /> <text:user-field-decl office:value-type="float" office:value="4165" text:name="ManuscriptAllWords" />
<text:user-field-decl office:value-type="float" office:value="3809" text:name="ManuscriptTextWords" /> <text:user-field-decl office:value-type="float" office:value="3811" text:name="ManuscriptTextWords" />
<text:user-field-decl office:value-type="float" office:value="54" text:name="ManuscriptTitleWords" /> <text:user-field-decl office:value-type="float" office:value="54" text:name="ManuscriptTitleWords" />
<text:user-field-decl office:value-type="float" office:value="27848" text:name="ManuscriptAllChars" /> <text:user-field-decl office:value-type="float" office:value="27869" text:name="ManuscriptAllChars" />
<text:user-field-decl office:value-type="float" office:value="25528" text:name="ManuscriptTextChars" /> <text:user-field-decl office:value-type="float" office:value="25549" text:name="ManuscriptTextChars" />
<text:user-field-decl office:value-type="float" office:value="310" text:name="ManuscriptTitleChars" /> <text:user-field-decl office:value-type="float" office:value="310" text:name="ManuscriptTitleChars" />
<text:user-field-decl office:value-type="float" office:value="23781" text:name="ManuscriptAllWordChars" /> <text:user-field-decl office:value-type="float" office:value="23801" text:name="ManuscriptAllWordChars" />
<text:user-field-decl office:value-type="float" office:value="21761" text:name="ManuscriptTextWordChars" /> <text:user-field-decl office:value-type="float" office:value="21781" text:name="ManuscriptTextWordChars" />
<text:user-field-decl office:value-type="float" office:value="275" text:name="ManuscriptTitleWordChars" /> <text:user-field-decl office:value-type="float" office:value="275" text:name="ManuscriptTitleWordChars" />
</text:user-field-decls> </text:user-field-decls>
<text:p text:style-name="Title">Lorem Ipsum</text:p> <text:p text:style-name="Title">Lorem Ipsum</text:p>
@@ -174,18 +177,19 @@
<text:p text:style-name="Footnote"><text:span text:style-name="T6">Lorem ipsum</text:span> is typically a corrupted version of De finibus bonorum et malorum, a 1st-century BC text by the Roman statesman and philosopher Cicero, with words altered, added, and removed to make it nonsensical and improper Latin. (Source: Wikipedia)</text:p> <text:p text:style-name="Footnote"><text:span text:style-name="T6">Lorem ipsum</text:span> is typically a corrupted version of De finibus bonorum et malorum, a 1st-century BC text by the Roman statesman and philosopher Cicero, with words altered, added, and removed to make it nonsensical and improper Latin. (Source: Wikipedia)</text:p>
</text:note-body> </text:note-body>
</text:note> of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</text:p> </text:note> of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</text:p>
<text:p text:style-name="P3">See <text:a xlink:type="simple" xlink:href="http://lipsum.com" text:style-name="T7">http://lipsum.com</text:a></text:p>
<text:h text:style-name="P7" text:outline-level="1">Part: Act One</text:h> <text:h text:style-name="P7" text:outline-level="1">Part: Act One</text:h>
<text:p text:style-name="P1">“Fusce maximus felis libero”</text:p> <text:p text:style-name="P1">“Fusce maximus felis libero”</text:p>
<text:h text:style-name="P4" text:outline-level="2">Chapter: Chapter One</text:h> <text:h text:style-name="P4" text:outline-level="2">Chapter: Chapter One</text:h>
<text:p text:style-name="P8"><text:span text:style-name="T7">Point of View:</text:span> <text:span text:style-name="T8">Bod</text:span></text:p> <text:p text:style-name="P8"><text:span text:style-name="T8">Point of View:</text:span> <text:span text:style-name="T9">Bod</text:span></text:p>
<text:p text:style-name="P9"><text:span text:style-name="T7">Plot:</text:span> <text:span text:style-name="T8">Main</text:span></text:p> <text:p text:style-name="P9"><text:span text:style-name="T8">Plot:</text:span> <text:span text:style-name="T9">Main</text:span></text:p>
<text:p text:style-name="Text_20_Meta"><text:span text:style-name="T7">Locations:</text:span> <text:span text:style-name="T8">Europe</text:span></text:p> <text:p text:style-name="Text_20_Meta"><text:span text:style-name="T8">Locations:</text:span> <text:span text:style-name="T9">Europe</text:span></text:p>
<text:p text:style-name="P5"><text:span text:style-name="T4">Synopsis:</text:span> <text:span text:style-name="T5">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque at aliquam quam.</text:span></text:p> <text:p text:style-name="P5"><text:span text:style-name="T4">Synopsis:</text:span> <text:span text:style-name="T5">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque at aliquam quam.</text:span></text:p>
<text:p text:style-name="P6">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque at aliquam quam. Praesent magna nunc, lacinia sit amet quam eget, aliquet ultrices justo. Morbi ornare enim et lorem rutrum finibus ut eu dolor. Aliquam a orci odio. Ut ultrices sem quis massa placerat, eget mollis nisl cursus. Cras vel sagittis justo. Ut non ultricies leo. Maecenas rutrum velit in est varius, et egestas massa pulvinar.</text:p> <text:p text:style-name="P6">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque at aliquam quam. Praesent magna nunc, lacinia sit amet quam eget, aliquet ultrices justo. Morbi ornare enim et lorem rutrum finibus ut eu dolor. Aliquam a orci odio. Ut ultrices sem quis massa placerat, eget mollis nisl cursus. Cras vel sagittis justo. Ut non ultricies leo. Maecenas rutrum velit in est varius, et egestas massa pulvinar.</text:p>
<text:h text:style-name="Heading_20_3" text:outline-level="3">Scene: Scene One</text:h> <text:h text:style-name="Heading_20_3" text:outline-level="3">Scene: Scene One</text:h>
<text:p text:style-name="P8"><text:span text:style-name="T7">Point of View:</text:span> <text:span text:style-name="T8">Bod</text:span></text:p> <text:p text:style-name="P8"><text:span text:style-name="T8">Point of View:</text:span> <text:span text:style-name="T9">Bod</text:span></text:p>
<text:p text:style-name="P9"><text:span text:style-name="T7">Plot:</text:span> <text:span text:style-name="T8">Main</text:span></text:p> <text:p text:style-name="P9"><text:span text:style-name="T8">Plot:</text:span> <text:span text:style-name="T9">Main</text:span></text:p>
<text:p text:style-name="Text_20_Meta"><text:span text:style-name="T7">Locations:</text:span> <text:span text:style-name="T8">Europe</text:span></text:p> <text:p text:style-name="Text_20_Meta"><text:span text:style-name="T8">Locations:</text:span> <text:span text:style-name="T9">Europe</text:span></text:p>
<text:p text:style-name="P5"><text:span text:style-name="T4">Synopsis:</text:span> <text:span text:style-name="T5">Aenean ut placerat velit. Etiam laoreet ullamcorper risus, eget lobortis enim scelerisque non. Suspendisse id maximus nunc, et mollis sapien. Curabitur vel semper sapien, non pulvinar dolor. Etiam finibus nisi vel mi molestie consectetur.</text:span></text:p> <text:p text:style-name="P5"><text:span text:style-name="T4">Synopsis:</text:span> <text:span text:style-name="T5">Aenean ut placerat velit. Etiam laoreet ullamcorper risus, eget lobortis enim scelerisque non. Suspendisse id maximus nunc, et mollis sapien. Curabitur vel semper sapien, non pulvinar dolor. Etiam finibus nisi vel mi molestie consectetur.</text:span></text:p>
<text:p text:style-name="P6">Aenean ut placerat velit. Etiam laoreet ullamcorper risus, eget lobortis enim scelerisque non. Suspendisse id maximus nunc, et mollis sapien. Curabitur vel semper sapien, non pulvinar dolor. Etiam finibus nisi vel mi molestie consectetur. Donec quis ante nunc. Mauris ut leo ipsum. Vestibulum est neque, hendrerit nec neque a, ullamcorper lobortis tellus. Fusce sollicitudin purus quis congue bibendum. Aliquam condimentum ipsum tristique blandit tristique. Donec pulvinar neque ac suscipit malesuada.</text:p> <text:p text:style-name="P6">Aenean ut placerat velit. Etiam laoreet ullamcorper risus, eget lobortis enim scelerisque non. Suspendisse id maximus nunc, et mollis sapien. Curabitur vel semper sapien, non pulvinar dolor. Etiam finibus nisi vel mi molestie consectetur. Donec quis ante nunc. Mauris ut leo ipsum. Vestibulum est neque, hendrerit nec neque a, ullamcorper lobortis tellus. Fusce sollicitudin purus quis congue bibendum. Aliquam condimentum ipsum tristique blandit tristique. Donec pulvinar neque ac suscipit malesuada.</text:p>
<text:p text:style-name="P3">Aliquam ut nisl arcu. Ut ultricies, lorem dignissim rutrum convallis, risus orci tempus lectus, congue feugiat sem lectus vitae odio. Duis sit amet justo finibus, hendrerit nulla at, ullamcorper enim. Praesent vel tellus sit amet tellus vulputate bibendum. Morbi eleifend sagittis sem, ac volutpat ante congue non. In hac habitasse platea dictumst. Morbi lobortis fermentum elit, dignissim sagittis ligula volutpat lacinia. Vestibulum eu interdum odio. Integer ac purus commodo metus congue tempor non at urna. Sed eget tortor vel quam viverra egestas. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Donec non convallis mauris, ac feugiat ex.</text:p> <text:p text:style-name="P3">Aliquam ut nisl arcu. Ut ultricies, lorem dignissim rutrum convallis, risus orci tempus lectus, congue feugiat sem lectus vitae odio. Duis sit amet justo finibus, hendrerit nulla at, ullamcorper enim. Praesent vel tellus sit amet tellus vulputate bibendum. Morbi eleifend sagittis sem, ac volutpat ante congue non. In hac habitasse platea dictumst. Morbi lobortis fermentum elit, dignissim sagittis ligula volutpat lacinia. Vestibulum eu interdum odio. Integer ac purus commodo metus congue tempor non at urna. Sed eget tortor vel quam viverra egestas. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Donec non convallis mauris, ac feugiat ex.</text:p>
@@ -193,9 +197,9 @@
<text:p text:style-name="P6">Integer vel libero ipsum. Donec varius aliquam libero, sit amet commodo urna hendrerit non. Nullam quis erat mollis nunc viverra volutpat tincidunt in odio. Nam vitae quam sem. Aliquam suscipit nulla non lorem pharetra semper. Ut suscipit erat eu ligula accumsan ultrices. Phasellus nisl tellus, placerat sed laoreet id, consectetur nec dolor. Sed fringilla ipsum id dapibus posuere. Aenean finibus pharetra tincidunt. Ut molestie malesuada nulla, id posuere lorem tincidunt eu. Aliquam tempor eros a est vulputate, scelerisque pulvinar ipsum fermentum. In hac habitasse platea dictumst.</text:p> <text:p text:style-name="P6">Integer vel libero ipsum. Donec varius aliquam libero, sit amet commodo urna hendrerit non. Nullam quis erat mollis nunc viverra volutpat tincidunt in odio. Nam vitae quam sem. Aliquam suscipit nulla non lorem pharetra semper. Ut suscipit erat eu ligula accumsan ultrices. Phasellus nisl tellus, placerat sed laoreet id, consectetur nec dolor. Sed fringilla ipsum id dapibus posuere. Aenean finibus pharetra tincidunt. Ut molestie malesuada nulla, id posuere lorem tincidunt eu. Aliquam tempor eros a est vulputate, scelerisque pulvinar ipsum fermentum. In hac habitasse platea dictumst.</text:p>
<text:p text:style-name="P3">Curabitur congue, justo quis interdum fermentum, tellus nulla imperdiet sapien, eu interdum enim tellus condimentum metus. Vivamus nunc velit, dignissim ut ultrices sit amet, ultricies quis enim. Donec ut vestibulum neque. Vivamus semper neque id ex ullamcorper varius. Fusce mattis nibh viverra lorem sagittis, et tempor arcu congue. Suspendisse sit amet felis sed urna facilisis mattis eget vitae arcu. Proin eu magna hendrerit, tristique sem maximus, placerat diam. Nulla tristique sed velit sit amet varius. Etiam vel ornare magna, in vulputate arcu. Cras velit orci, tincidunt sed volutpat cursus, bibendum vel sem. Nunc vulputate pharetra tortor, ac consectetur neque tincidunt sit amet. Nulla ornare mi sed mi dignissim ultricies. Ut tincidunt bibendum mauris, sed elementum ex vulputate vel. Mauris fermentum, felis nec vehicula congue, felis lorem facilisis erat, a dictum dolor augue vitae quam. Maecenas rutrum tortor nec consequat eleifend.</text:p> <text:p text:style-name="P3">Curabitur congue, justo quis interdum fermentum, tellus nulla imperdiet sapien, eu interdum enim tellus condimentum metus. Vivamus nunc velit, dignissim ut ultrices sit amet, ultricies quis enim. Donec ut vestibulum neque. Vivamus semper neque id ex ullamcorper varius. Fusce mattis nibh viverra lorem sagittis, et tempor arcu congue. Suspendisse sit amet felis sed urna facilisis mattis eget vitae arcu. Proin eu magna hendrerit, tristique sem maximus, placerat diam. Nulla tristique sed velit sit amet varius. Etiam vel ornare magna, in vulputate arcu. Cras velit orci, tincidunt sed volutpat cursus, bibendum vel sem. Nunc vulputate pharetra tortor, ac consectetur neque tincidunt sit amet. Nulla ornare mi sed mi dignissim ultricies. Ut tincidunt bibendum mauris, sed elementum ex vulputate vel. Mauris fermentum, felis nec vehicula congue, felis lorem facilisis erat, a dictum dolor augue vitae quam. Maecenas rutrum tortor nec consequat eleifend.</text:p>
<text:h text:style-name="Heading_20_3" text:outline-level="3">Scene: Scene Two</text:h> <text:h text:style-name="Heading_20_3" text:outline-level="3">Scene: Scene Two</text:h>
<text:p text:style-name="P8"><text:span text:style-name="T7">Point of View:</text:span> <text:span text:style-name="T8">Bod</text:span></text:p> <text:p text:style-name="P8"><text:span text:style-name="T8">Point of View:</text:span> <text:span text:style-name="T9">Bod</text:span></text:p>
<text:p text:style-name="P9"><text:span text:style-name="T7">Plot:</text:span> <text:span text:style-name="T8">Main</text:span></text:p> <text:p text:style-name="P9"><text:span text:style-name="T8">Plot:</text:span> <text:span text:style-name="T9">Main</text:span></text:p>
<text:p text:style-name="Text_20_Meta"><text:span text:style-name="T7">Locations:</text:span> <text:span text:style-name="T8">Europe</text:span></text:p> <text:p text:style-name="Text_20_Meta"><text:span text:style-name="T8">Locations:</text:span> <text:span text:style-name="T9">Europe</text:span></text:p>
<text:p text:style-name="P5"><text:span text:style-name="T4">Synopsis:</text:span> <text:span text:style-name="T5">Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer sapien nulla, dictum at lacus a, dignissim consectetur dolor. Nunc vel eleifend lacus, eu dapibus orci.</text:span></text:p> <text:p text:style-name="P5"><text:span text:style-name="T4">Synopsis:</text:span> <text:span text:style-name="T5">Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer sapien nulla, dictum at lacus a, dignissim consectetur dolor. Nunc vel eleifend lacus, eu dapibus orci.</text:span></text:p>
<text:p text:style-name="P6">Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer sapien nulla, dictum at lacus a, dignissim consectetur dolor. Nunc vel eleifend lacus, eu dapibus orci. Vestibulum facilisis bibendum aliquam. Aliquam posuere, turpis ac bibendum varius, sem tellus venenatis risus, in elementum massa enim ac lorem. Integer in sem ac diam blandit ultricies ut in nulla. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam sit amet erat est. Curabitur vitae cursus justo, sit amet placerat dolor. Vivamus eu felis hendrerit, tincidunt massa rutrum, maximus arcu. Pellentesque commodo justo odio, vel rutrum nulla tincidunt eu. Integer non neque condimentum, convallis diam non, varius ligula. Aliquam eget sapien mauris. Aenean pharetra nunc nisi, vel maximus ante tristique sit amet. Aliquam risus metus, interdum non odio eu, consectetur lacinia sapien.</text:p> <text:p text:style-name="P6">Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer sapien nulla, dictum at lacus a, dignissim consectetur dolor. Nunc vel eleifend lacus, eu dapibus orci. Vestibulum facilisis bibendum aliquam. Aliquam posuere, turpis ac bibendum varius, sem tellus venenatis risus, in elementum massa enim ac lorem. Integer in sem ac diam blandit ultricies ut in nulla. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam sit amet erat est. Curabitur vitae cursus justo, sit amet placerat dolor. Vivamus eu felis hendrerit, tincidunt massa rutrum, maximus arcu. Pellentesque commodo justo odio, vel rutrum nulla tincidunt eu. Integer non neque condimentum, convallis diam non, varius ligula. Aliquam eget sapien mauris. Aenean pharetra nunc nisi, vel maximus ante tristique sit amet. Aliquam risus metus, interdum non odio eu, consectetur lacinia sapien.</text:p>
<text:p text:style-name="P3">Proin vitae gravida nisl. Integer viverra orci turpis, sit amet pretium ligula facilisis consequat. Nulla interdum commodo metus, mollis consequat dui tincidunt et. Proin consequat bibendum justo id commodo. Fusce fermentum nunc turpis, eu vestibulum risus feugiat ut. Sed scelerisque vel ligula ut interdum. Suspendisse ac blandit ligula, sagittis fringilla dolor. In tincidunt convallis diam et ornare. Aenean id dignissim est, ut rhoncus quam. Donec vitae nisl velit. In convallis nibh ut augue dignissim, eu elementum quam cursus. Phasellus in lectus lorem. Curabitur in pellentesque nisi, at gravida sapien. Sed cursus justo volutpat lacus placerat, sit amet dignissim turpis commodo. Aliquam vitae orci eget nulla posuere condimentum in ut felis.</text:p> <text:p text:style-name="P3">Proin vitae gravida nisl. Integer viverra orci turpis, sit amet pretium ligula facilisis consequat. Nulla interdum commodo metus, mollis consequat dui tincidunt et. Proin consequat bibendum justo id commodo. Fusce fermentum nunc turpis, eu vestibulum risus feugiat ut. Sed scelerisque vel ligula ut interdum. Suspendisse ac blandit ligula, sagittis fringilla dolor. In tincidunt convallis diam et ornare. Aenean id dignissim est, ut rhoncus quam. Donec vitae nisl velit. In convallis nibh ut augue dignissim, eu elementum quam cursus. Phasellus in lectus lorem. Curabitur in pellentesque nisi, at gravida sapien. Sed cursus justo volutpat lacus placerat, sit amet dignissim turpis commodo. Aliquam vitae orci eget nulla posuere condimentum in ut felis.</text:p>
@@ -210,24 +214,24 @@
<text:p text:style-name="P3"><text:tab />The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English.</text:p> <text:p text:style-name="P3"><text:tab />The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English.</text:p>
<text:p text:style-name="P3"><text:tab />Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like).</text:p> <text:p text:style-name="P3"><text:tab />Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like).</text:p>
<text:h text:style-name="P4" text:outline-level="2">Chapter: Chapter Two</text:h> <text:h text:style-name="P4" text:outline-level="2">Chapter: Chapter Two</text:h>
<text:p text:style-name="P8"><text:span text:style-name="T7">Point of View:</text:span> <text:span text:style-name="T8">Bod</text:span></text:p> <text:p text:style-name="P8"><text:span text:style-name="T8">Point of View:</text:span> <text:span text:style-name="T9">Bod</text:span></text:p>
<text:p text:style-name="P9"><text:span text:style-name="T7">Plot:</text:span> <text:span text:style-name="T8">Main</text:span></text:p> <text:p text:style-name="P9"><text:span text:style-name="T8">Plot:</text:span> <text:span text:style-name="T9">Main</text:span></text:p>
<text:p text:style-name="Text_20_Meta"><text:span text:style-name="T7">Locations:</text:span> <text:span text:style-name="T8">Europe</text:span></text:p> <text:p text:style-name="Text_20_Meta"><text:span text:style-name="T8">Locations:</text:span> <text:span text:style-name="T9">Europe</text:span></text:p>
<text:p text:style-name="P5"><text:span text:style-name="T4">Synopsis:</text:span> <text:span text:style-name="T5">Curabitur a elit posuere, varius ex et, convallis neque. Phasellus sagittis pharetra sem vitae dapibus. Curabitur varius lorem non pulvinar congue.</text:span></text:p> <text:p text:style-name="P5"><text:span text:style-name="T4">Synopsis:</text:span> <text:span text:style-name="T5">Curabitur a elit posuere, varius ex et, convallis neque. Phasellus sagittis pharetra sem vitae dapibus. Curabitur varius lorem non pulvinar congue.</text:span></text:p>
<text:p text:style-name="P6">Curabitur a elit posuere, varius ex et, convallis neque. Phasellus sagittis pharetra sem vitae dapibus. Curabitur varius lorem non pulvinar congue. Vestibulum pharetra fermentum leo, sed faucibus eros placerat quis. In hac habitasse platea dictumst. Donec metus massa, rutrum quis consequat et, tincidunt ac felis. Duis mollis metus ac nunc tincidunt blandit. Ut aliquet velit eu odio pharetra condimentum. Integer rutrum lacus orci, id venenatis libero accumsan at.</text:p> <text:p text:style-name="P6">Curabitur a elit posuere, varius ex et, convallis neque. Phasellus sagittis pharetra sem vitae dapibus. Curabitur varius lorem non pulvinar congue. Vestibulum pharetra fermentum leo, sed faucibus eros placerat quis. In hac habitasse platea dictumst. Donec metus massa, rutrum quis consequat et, tincidunt ac felis. Duis mollis metus ac nunc tincidunt blandit. Ut aliquet velit eu odio pharetra condimentum. Integer rutrum lacus orci, id venenatis libero accumsan at.</text:p>
<text:h text:style-name="Heading_20_3" text:outline-level="3">Scene: Scene Three</text:h> <text:h text:style-name="Heading_20_3" text:outline-level="3">Scene: Scene Three</text:h>
<text:p text:style-name="P8"><text:span text:style-name="T7">Point of View:</text:span> <text:span text:style-name="T8">Bod</text:span></text:p> <text:p text:style-name="P8"><text:span text:style-name="T8">Point of View:</text:span> <text:span text:style-name="T9">Bod</text:span></text:p>
<text:p text:style-name="P9"><text:span text:style-name="T7">Plot:</text:span> <text:span text:style-name="T8">Main</text:span></text:p> <text:p text:style-name="P9"><text:span text:style-name="T8">Plot:</text:span> <text:span text:style-name="T9">Main</text:span></text:p>
<text:p text:style-name="Text_20_Meta"><text:span text:style-name="T7">Locations:</text:span> <text:span text:style-name="T8">Europe</text:span></text:p> <text:p text:style-name="Text_20_Meta"><text:span text:style-name="T8">Locations:</text:span> <text:span text:style-name="T9">Europe</text:span></text:p>
<text:p text:style-name="P5"><text:span text:style-name="T4">Synopsis:</text:span> <text:span text:style-name="T5">Aenean ut libero ut lectus porttitor rhoncus vel et massa. Nam pretium, nibh et varius vehicula, urna metus blandit eros, euismod pharetra diam diam et libero. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.</text:span></text:p> <text:p text:style-name="P5"><text:span text:style-name="T4">Synopsis:</text:span> <text:span text:style-name="T5">Aenean ut libero ut lectus porttitor rhoncus vel et massa. Nam pretium, nibh et varius vehicula, urna metus blandit eros, euismod pharetra diam diam et libero. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.</text:span></text:p>
<text:p text:style-name="P6">Aenean ut libero ut lectus porttitor rhoncus vel et massa. Nam pretium, nibh et varius vehicula, urna metus blandit eros, euismod pharetra diam diam et libero. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean tincidunt lacus vitae nibh elementum eleifend. Sed rutrum condimentum sem quis blandit. Duis imperdiet libero metus, quis convallis quam faucibus a. Nulla ligula est, semper quis sollicitudin et, pretium id justo. Curabitur pharetra risus eget consectetur commodo. Duis mattis arcu non est condimentum, id venenatis risus volutpat. Pellentesque aliquet mauris non mauris porttitor ultrices. Phasellus ut vestibulum mi. Suspendisse malesuada metus lorem, a malesuada orci rhoncus a. Praesent euismod convallis ante, lacinia tincidunt ex egestas id. Praesent sit amet efficitur sapien. Morbi tincidunt volutpat nunc sed dictum. Aliquam ultrices metus id fermentum lobortis.</text:p> <text:p text:style-name="P6">Aenean ut libero ut lectus porttitor rhoncus vel et massa. Nam pretium, nibh et varius vehicula, urna metus blandit eros, euismod pharetra diam diam et libero. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean tincidunt lacus vitae nibh elementum eleifend. Sed rutrum condimentum sem quis blandit. Duis imperdiet libero metus, quis convallis quam faucibus a. Nulla ligula est, semper quis sollicitudin et, pretium id justo. Curabitur pharetra risus eget consectetur commodo. Duis mattis arcu non est condimentum, id venenatis risus volutpat. Pellentesque aliquet mauris non mauris porttitor ultrices. Phasellus ut vestibulum mi. Suspendisse malesuada metus lorem, a malesuada orci rhoncus a. Praesent euismod convallis ante, lacinia tincidunt ex egestas id. Praesent sit amet efficitur sapien. Morbi tincidunt volutpat nunc sed dictum. Aliquam ultrices metus id fermentum lobortis.</text:p>
<text:p text:style-name="P3">Pellentesque id sagittis dui. Praesent ut nisi sit amet libero euismod ornare. Vestibulum vehicula, lorem eget aliquet imperdiet, eros nulla iaculis mi, vel bibendum est dui sed orci. Nullam vitae lorem rutrum, euismod lacus id, ullamcorper lectus. Duis nec commodo mi, a fringilla diam. Vestibulum molestie nibh tristique, viverra augue non, aliquet metus. Phasellus a tellus ac nisl tempor aliquet. Nulla vitae sapien rutrum augue ornare ultrices a quis nisi. Sed pulvinar tincidunt ex. Fusce vel sem vitae ante pellentesque lobortis.</text:p> <text:p text:style-name="P3">Pellentesque id sagittis dui. Praesent ut nisi sit amet libero euismod ornare. Vestibulum vehicula, lorem eget aliquet imperdiet, eros nulla iaculis mi, vel bibendum est dui sed orci. Nullam vitae lorem rutrum, euismod lacus id, ullamcorper lectus. Duis nec commodo mi, a fringilla diam. Vestibulum molestie nibh tristique, viverra augue non, aliquet metus. Phasellus a tellus ac nisl tempor aliquet. Nulla vitae sapien rutrum augue ornare ultrices a quis nisi. Sed pulvinar tincidunt ex. Fusce vel sem vitae ante pellentesque lobortis.</text:p>
<text:p text:style-name="P3">Maecenas ullamcorper lacus nec turpis finibus aliquet eget rutrum augue. Integer lorem erat, faucibus non lacus lacinia, pulvinar egestas felis. Proin rutrum nunc eget nulla varius, id blandit mauris tincidunt. Donec sit amet ullamcorper nisi, ut efficitur mi. Aliquam aliquet, nulla eget rhoncus tristique, justo lorem consectetur dui, id ornare leo odio sed tellus. Curabitur interdum velit a turpis condimentum venenatis. Nunc rhoncus sem ac augue auctor, nec malesuada ex fringilla. Vestibulum egestas diam sed leo consectetur vulputate quis eget enim. Nam tincidunt metus sit amet maximus ullamcorper. Sed placerat velit vitae massa efficitur viverra. Etiam eleifend dignissim ante, sed luctus nisl tristique a. In vestibulum pharetra dolor in molestie. Vivamus auctor massa ac magna imperdiet, sit amet iaculis turpis finibus.</text:p> <text:p text:style-name="P3">Maecenas ullamcorper lacus nec turpis finibus aliquet eget rutrum augue. Integer lorem erat, faucibus non lacus lacinia, pulvinar egestas felis. Proin rutrum nunc eget nulla varius, id blandit mauris tincidunt. Donec sit amet ullamcorper nisi, ut efficitur mi. Aliquam aliquet, nulla eget rhoncus tristique, justo lorem consectetur dui, id ornare leo odio sed tellus. Curabitur interdum velit a turpis condimentum venenatis. Nunc rhoncus sem ac augue auctor, nec malesuada ex fringilla. Vestibulum egestas diam sed leo consectetur vulputate quis eget enim. Nam tincidunt metus sit amet maximus ullamcorper. Sed placerat velit vitae massa efficitur viverra. Etiam eleifend dignissim ante, sed luctus nisl tristique a. In vestibulum pharetra dolor in molestie. Vivamus auctor massa ac magna imperdiet, sit amet iaculis turpis finibus.</text:p>
<text:p text:style-name="P3">Aenean dapibus vulputate purus, sit amet tempor nunc suscipit consequat. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Mauris auctor congue eros, non pellentesque neque dapibus ac. Vestibulum non leo nec urna lacinia eleifend quis et diam. Praesent eu nisi magna. Nulla at magna massa. Suspendisse porta varius scelerisque. Duis at auctor dolor, non dapibus urna. Nunc venenatis feugiat magna non molestie. Aliquam non ornare ex. Quisque eu ultrices velit, quis pellentesque eros. Phasellus eleifend, elit id imperdiet aliquam, nulla quam molestie turpis, at egestas odio ante et tortor. Suspendisse fringilla condimentum justo, at aliquet odio aliquam ac.</text:p> <text:p text:style-name="P3">Aenean dapibus vulputate purus, sit amet tempor nunc suscipit consequat. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Mauris auctor congue eros, non pellentesque neque dapibus ac. Vestibulum non leo nec urna lacinia eleifend quis et diam. Praesent eu nisi magna. Nulla at magna massa. Suspendisse porta varius scelerisque. Duis at auctor dolor, non dapibus urna. Nunc venenatis feugiat magna non molestie. Aliquam non ornare ex. Quisque eu ultrices velit, quis pellentesque eros. Phasellus eleifend, elit id imperdiet aliquam, nulla quam molestie turpis, at egestas odio ante et tortor. Suspendisse fringilla condimentum justo, at aliquet odio aliquam ac.</text:p>
<text:h text:style-name="Heading_20_3" text:outline-level="3">Scene: Scene Four</text:h> <text:h text:style-name="Heading_20_3" text:outline-level="3">Scene: Scene Four</text:h>
<text:p text:style-name="P8"><text:span text:style-name="T7">Point of View:</text:span> <text:span text:style-name="T8">Bod</text:span></text:p> <text:p text:style-name="P8"><text:span text:style-name="T8">Point of View:</text:span> <text:span text:style-name="T9">Bod</text:span></text:p>
<text:p text:style-name="P9"><text:span text:style-name="T7">Plot:</text:span> <text:span text:style-name="T8">Main</text:span></text:p> <text:p text:style-name="P9"><text:span text:style-name="T8">Plot:</text:span> <text:span text:style-name="T9">Main</text:span></text:p>
<text:p text:style-name="Text_20_Meta"><text:span text:style-name="T7">Locations:</text:span> <text:span text:style-name="T8">Europe</text:span></text:p> <text:p text:style-name="Text_20_Meta"><text:span text:style-name="T8">Locations:</text:span> <text:span text:style-name="T9">Europe</text:span></text:p>
<text:p text:style-name="P5"><text:span text:style-name="T4">Synopsis:</text:span> <text:span text:style-name="T5">Nam tempor blandit magna laoreet aliquet. Vestibulum auctor posuere leo, ac gravida nisi rhoncus varius. Aenean posuere dolor vitae condimentum volutpat. Donec egestas volutpat risus, quis luctus justo.</text:span></text:p> <text:p text:style-name="P5"><text:span text:style-name="T4">Synopsis:</text:span> <text:span text:style-name="T5">Nam tempor blandit magna laoreet aliquet. Vestibulum auctor posuere leo, ac gravida nisi rhoncus varius. Aenean posuere dolor vitae condimentum volutpat. Donec egestas volutpat risus, quis luctus justo.</text:span></text:p>
<text:p text:style-name="P6">Nam tempor blandit magna laoreet aliquet. Vestibulum auctor posuere leo, ac gravida nisi rhoncus varius. Aenean posuere dolor vitae condimentum volutpat. Donec egestas volutpat risus, quis luctus justo. Nullam viverra dui et auctor pretium. Ut ullamcorper velit urna, sed imperdiet massa convallis a. Suspendisse efficitur, ipsum nec cursus pulvinar, eros urna posuere diam, nec elementum mi felis vitae sapien.</text:p> <text:p text:style-name="P6">Nam tempor blandit magna laoreet aliquet. Vestibulum auctor posuere leo, ac gravida nisi rhoncus varius. Aenean posuere dolor vitae condimentum volutpat. Donec egestas volutpat risus, quis luctus justo. Nullam viverra dui et auctor pretium. Ut ullamcorper velit urna, sed imperdiet massa convallis a. Suspendisse efficitur, ipsum nec cursus pulvinar, eros urna posuere diam, nec elementum mi felis vitae sapien.</text:p>
<text:p text:style-name="P3">Duis efficitur metus pulvinar, molestie magna eget, feugiat dui. Fusce convallis vehicula ipsum convallis blandit. Duis eros risus, malesuada eu imperdiet in, hendrerit ac metus. Vestibulum id justo gravida, dignissim nibh non, iaculis diam. Fusce accumsan est ut massa porta ultricies. Nulla vitae justo in tortor laoreet mollis. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin eu libero justo. Vivamus aliquet placerat est, et auctor eros posuere venenatis. Nunc quam diam, tincidunt ac aliquet in, fermentum sit amet lectus. Proin commodo tincidunt blandit. Quisque erat arcu, semper nec dui non, consectetur gravida ipsum. Nullam pretium consectetur elit at condimentum.</text:p> <text:p text:style-name="P3">Duis efficitur metus pulvinar, molestie magna eget, feugiat dui. Fusce convallis vehicula ipsum convallis blandit. Duis eros risus, malesuada eu imperdiet in, hendrerit ac metus. Vestibulum id justo gravida, dignissim nibh non, iaculis diam. Fusce accumsan est ut massa porta ultricies. Nulla vitae justo in tortor laoreet mollis. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin eu libero justo. Vivamus aliquet placerat est, et auctor eros posuere venenatis. Nunc quam diam, tincidunt ac aliquet in, fermentum sit amet lectus. Proin commodo tincidunt blandit. Quisque erat arcu, semper nec dui non, consectetur gravida ipsum. Nullam pretium consectetur elit at condimentum.</text:p>
@@ -236,9 +240,9 @@
<text:p text:style-name="P3">Donec ipsum eros, vestibulum sit amet cursus eget, iaculis quis dolor. Pellentesque magna augue, tristique dapibus mi vitae, molestie venenatis enim. Nam malesuada, turpis volutpat rhoncus ullamcorper, justo est eleifend orci, ut luctus risus ex rutrum arcu. Sed mi elit, feugiat rhoncus ornare sed, porta id leo. Pellentesque feugiat nulla tincidunt erat suscipit, eu congue lacus hendrerit. Morbi pulvinar enim sed consequat auctor. Ut eleifend enim sem, vitae euismod ex ultricies sit amet. Curabitur eu efficitur nisi, suscipit finibus sapien. In sodales blandit erat, vestibulum pulvinar ante volutpat nec. Vivamus dictum non libero at molestie. Donec sit amet neque in ante convallis pretium. Nunc vel iaculis dui.</text:p> <text:p text:style-name="P3">Donec ipsum eros, vestibulum sit amet cursus eget, iaculis quis dolor. Pellentesque magna augue, tristique dapibus mi vitae, molestie venenatis enim. Nam malesuada, turpis volutpat rhoncus ullamcorper, justo est eleifend orci, ut luctus risus ex rutrum arcu. Sed mi elit, feugiat rhoncus ornare sed, porta id leo. Pellentesque feugiat nulla tincidunt erat suscipit, eu congue lacus hendrerit. Morbi pulvinar enim sed consequat auctor. Ut eleifend enim sem, vitae euismod ex ultricies sit amet. Curabitur eu efficitur nisi, suscipit finibus sapien. In sodales blandit erat, vestibulum pulvinar ante volutpat nec. Vivamus dictum non libero at molestie. Donec sit amet neque in ante convallis pretium. Nunc vel iaculis dui.</text:p>
<text:p text:style-name="P3">Phasellus eu nunc ut nunc faucibus laoreet. Aliquam at magna risus. Praesent lobortis, risus finibus semper varius, magna purus vestibulum eros, at pulvinar sapien enim a ex. In scelerisque malesuada ex, sit amet egestas neque condimentum sed. Praesent vulputate efficitur massa. Cras at accumsan ligula. In elementum lectus eget blandit dictum. Nam vitae libero ut justo eleifend rutrum ac nec arcu. Aliquam sodales in quam congue vestibulum. Aliquam in accumsan sapien. Quisque lobortis nisl nisi, vitae bibendum turpis efficitur sed. Vestibulum tempor nulla eget nisi convallis, blandit sagittis ipsum convallis. Donec odio nibh, ultrices quis odio in, mollis euismod libero.</text:p> <text:p text:style-name="P3">Phasellus eu nunc ut nunc faucibus laoreet. Aliquam at magna risus. Praesent lobortis, risus finibus semper varius, magna purus vestibulum eros, at pulvinar sapien enim a ex. In scelerisque malesuada ex, sit amet egestas neque condimentum sed. Praesent vulputate efficitur massa. Cras at accumsan ligula. In elementum lectus eget blandit dictum. Nam vitae libero ut justo eleifend rutrum ac nec arcu. Aliquam sodales in quam congue vestibulum. Aliquam in accumsan sapien. Quisque lobortis nisl nisi, vitae bibendum turpis efficitur sed. Vestibulum tempor nulla eget nisi convallis, blandit sagittis ipsum convallis. Donec odio nibh, ultrices quis odio in, mollis euismod libero.</text:p>
<text:h text:style-name="Heading_20_3" text:outline-level="3">Scene: Scene Five</text:h> <text:h text:style-name="Heading_20_3" text:outline-level="3">Scene: Scene Five</text:h>
<text:p text:style-name="P8"><text:span text:style-name="T7">Point of View:</text:span> <text:span text:style-name="T8">Bod</text:span></text:p> <text:p text:style-name="P8"><text:span text:style-name="T8">Point of View:</text:span> <text:span text:style-name="T9">Bod</text:span></text:p>
<text:p text:style-name="P9"><text:span text:style-name="T7">Plot:</text:span> <text:span text:style-name="T8">Main</text:span></text:p> <text:p text:style-name="P9"><text:span text:style-name="T8">Plot:</text:span> <text:span text:style-name="T9">Main</text:span></text:p>
<text:p text:style-name="Text_20_Meta"><text:span text:style-name="T7">Locations:</text:span> <text:span text:style-name="T8">Europe</text:span></text:p> <text:p text:style-name="Text_20_Meta"><text:span text:style-name="T8">Locations:</text:span> <text:span text:style-name="T9">Europe</text:span></text:p>
<text:p text:style-name="P5"><text:span text:style-name="T4">Synopsis:</text:span> <text:span text:style-name="T5">Praesent eget est porta, dictum ante in, egestas risus. Mauris risus mauris, consequat aliquam mauris et, feugiat iaculis ipsum. Aliquam arcu ipsum, fermentum ut arcu sed, lobortis euismod sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.</text:span></text:p> <text:p text:style-name="P5"><text:span text:style-name="T4">Synopsis:</text:span> <text:span text:style-name="T5">Praesent eget est porta, dictum ante in, egestas risus. Mauris risus mauris, consequat aliquam mauris et, feugiat iaculis ipsum. Aliquam arcu ipsum, fermentum ut arcu sed, lobortis euismod sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.</text:span></text:p>
<text:p text:style-name="P6">Praesent eget est porta, dictum ante in, egestas risus. Mauris risus mauris, consequat aliquam mauris et, feugiat iaculis ipsum. Aliquam arcu ipsum, fermentum ut arcu sed, lobortis euismod sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In sed felis auctor, rhoncus dui ac, consequat dolor. Integer volutpat libero sed nisl aliquet varius. Suspendisse et lorem sapien. Proin id ultrices nibh, ac suscipit diam. Suspendisse placerat varius porttitor. Curabitur elementum sed enim ultrices imperdiet.</text:p> <text:p text:style-name="P6">Praesent eget est porta, dictum ante in, egestas risus. Mauris risus mauris, consequat aliquam mauris et, feugiat iaculis ipsum. Aliquam arcu ipsum, fermentum ut arcu sed, lobortis euismod sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In sed felis auctor, rhoncus dui ac, consequat dolor. Integer volutpat libero sed nisl aliquet varius. Suspendisse et lorem sapien. Proin id ultrices nibh, ac suscipit diam. Suspendisse placerat varius porttitor. Curabitur elementum sed enim ultrices imperdiet.</text:p>
<text:p text:style-name="P3">In ut lobortis lacus, nec luctus arcu. Vivamus condimentum sapien a ipsum malesuada sodales. Donec et vestibulum risus. Integer dictum euismod eros id tincidunt. Aliquam sagittis leo vitae consequat fermentum. Donec maximus ex eu ex iaculis porta. Praesent pharetra lacinia risus, et eleifend diam commodo non. Sed feugiat ipsum ut orci sagittis, quis faucibus lectus blandit. Sed tellus quam, gravida vitae laoreet quis, tempus lobortis dui. Vivamus semper accumsan ullamcorper. Praesent tempus pretium eros, non elementum risus. Pellentesque odio quam, auctor quis ex non, vulputate egestas dolor. Nunc luctus enim ut justo sodales consectetur. Sed aliquet a mauris vel posuere.</text:p> <text:p text:style-name="P3">In ut lobortis lacus, nec luctus arcu. Vivamus condimentum sapien a ipsum malesuada sodales. Donec et vestibulum risus. Integer dictum euismod eros id tincidunt. Aliquam sagittis leo vitae consequat fermentum. Donec maximus ex eu ex iaculis porta. Praesent pharetra lacinia risus, et eleifend diam commodo non. Sed feugiat ipsum ut orci sagittis, quis faucibus lectus blandit. Sed tellus quam, gravida vitae laoreet quis, tempus lobortis dui. Vivamus semper accumsan ullamcorper. Praesent tempus pretium eros, non elementum risus. Pellentesque odio quam, auctor quis ex non, vulputate egestas dolor. Nunc luctus enim ut justo sodales consectetur. Sed aliquet a mauris vel posuere.</text:p>
@@ -247,19 +251,19 @@
<text:p text:style-name="P3">Integer egestas maximus leo eu facilisis. Nunc rhoncus dignissim lectus eu lacinia. Praesent lacinia urna porttitor aliquam condimentum. Nulla eu eros dictum, dictum nunc vitae, sagittis nibh. Integer ante neque, consequat nec sollicitudin id, consectetur vitae dolor. Nullam volutpat sem orci, quis viverra magna auctor a. Suspendisse potenti. Maecenas commodo sed neque pellentesque vehicula. Sed luctus nisl risus, elementum semper purus interdum vel. Ut pulvinar, massa sit amet venenatis placerat, nunc lacus hendrerit odio, non aliquet nunc risus eu lectus. Maecenas feugiat semper ligula, id lobortis sem porta eu. Integer posuere elit magna, at mollis eros bibendum et. Ut imperdiet purus vel nulla aliquam maximus. Morbi sodales purus tellus, a rhoncus sem rutrum sit amet. Quisque risus sem, laoreet nec convallis nec, rutrum vitae justo.</text:p> <text:p text:style-name="P3">Integer egestas maximus leo eu facilisis. Nunc rhoncus dignissim lectus eu lacinia. Praesent lacinia urna porttitor aliquam condimentum. Nulla eu eros dictum, dictum nunc vitae, sagittis nibh. Integer ante neque, consequat nec sollicitudin id, consectetur vitae dolor. Nullam volutpat sem orci, quis viverra magna auctor a. Suspendisse potenti. Maecenas commodo sed neque pellentesque vehicula. Sed luctus nisl risus, elementum semper purus interdum vel. Ut pulvinar, massa sit amet venenatis placerat, nunc lacus hendrerit odio, non aliquet nunc risus eu lectus. Maecenas feugiat semper ligula, id lobortis sem porta eu. Integer posuere elit magna, at mollis eros bibendum et. Ut imperdiet purus vel nulla aliquam maximus. Morbi sodales purus tellus, a rhoncus sem rutrum sit amet. Quisque risus sem, laoreet nec convallis nec, rutrum vitae justo.</text:p>
<text:p text:style-name="P10">Notes: Characters</text:p> <text:p text:style-name="P10">Notes: Characters</text:p>
<text:h text:style-name="Heading_20_1" text:outline-level="1">Nobody Owens</text:h> <text:h text:style-name="Heading_20_1" text:outline-level="1">Nobody Owens</text:h>
<text:p text:style-name="P8"><text:span text:style-name="T7">Tag:</text:span> <text:span text:style-name="T8">Bod</text:span> | <text:span text:style-name="T9">Nobody Owens</text:span></text:p> <text:p text:style-name="P8"><text:span text:style-name="T8">Tag:</text:span> <text:span text:style-name="T9">Bod</text:span> | <text:span text:style-name="T10">Nobody Owens</text:span></text:p>
<text:p text:style-name="Text_20_Meta"><text:span text:style-name="T7">Plot:</text:span> <text:span text:style-name="T8">Main</text:span></text:p> <text:p text:style-name="Text_20_Meta"><text:span text:style-name="T8">Plot:</text:span> <text:span text:style-name="T9">Main</text:span></text:p>
<text:p text:style-name="P6">Pellentesque nec erat ut nulla posuere commodo. Curabitur nisi augue, imperdiet et porta imperdiet, efficitur id leo. Cras finibus arcu at nibh commodo congue. Proin suscipit placerat condimentum. Aenean ante enim, cursus id lorem a, blandit venenatis nibh. Maecenas suscipit porta elit, sit amet porta felis porttitor eu. Sed a dui nibh. Phasellus sed faucibus dui. Pellentesque felis nulla, ultrices non efficitur quis, rutrum id mi. Mauris tempus auctor nisl, in bibendum enim pellentesque sit amet. Proin nunc lacus, imperdiet nec posuere ac, interdum non lectus.</text:p> <text:p text:style-name="P6">Pellentesque nec erat ut nulla posuere commodo. Curabitur nisi augue, imperdiet et porta imperdiet, efficitur id leo. Cras finibus arcu at nibh commodo congue. Proin suscipit placerat condimentum. Aenean ante enim, cursus id lorem a, blandit venenatis nibh. Maecenas suscipit porta elit, sit amet porta felis porttitor eu. Sed a dui nibh. Phasellus sed faucibus dui. Pellentesque felis nulla, ultrices non efficitur quis, rutrum id mi. Mauris tempus auctor nisl, in bibendum enim pellentesque sit amet. Proin nunc lacus, imperdiet nec posuere ac, interdum non lectus.</text:p>
<text:p text:style-name="P3">Suspendisse faucibus est auctor orci mollis luctus. Praesent quis sodales neque. Interdum et malesuada fames ac ante ipsum primis in faucibus. Donec sodales rutrum mattis. In in sem ornare, consequat nulla ac, convallis arcu. Duis ac metus id felis commodo commodo sit amet eget diam. Curabitur rhoncus lacinia leo at sodales. Etiam finibus porta diam a viverra. Praesent nisi urna, volutpat sit amet odio at, vehicula vehicula leo. In non enim eget nisl luctus commodo. Pellentesque pellentesque at lectus at luctus. Quisque nec felis bibendum, lacinia libero ut, lacinia eros. Integer finibus ultricies nibh sit amet placerat.</text:p> <text:p text:style-name="P3">Suspendisse faucibus est auctor orci mollis luctus. Praesent quis sodales neque. Interdum et malesuada fames ac ante ipsum primis in faucibus. Donec sodales rutrum mattis. In in sem ornare, consequat nulla ac, convallis arcu. Duis ac metus id felis commodo commodo sit amet eget diam. Curabitur rhoncus lacinia leo at sodales. Etiam finibus porta diam a viverra. Praesent nisi urna, volutpat sit amet odio at, vehicula vehicula leo. In non enim eget nisl luctus commodo. Pellentesque pellentesque at lectus at luctus. Quisque nec felis bibendum, lacinia libero ut, lacinia eros. Integer finibus ultricies nibh sit amet placerat.</text:p>
<text:p text:style-name="P3">Nullam scelerisque velit et tortor congue vestibulum a at nisi. Vivamus sodales ut turpis a convallis. In dignissim nibh at luctus sodales. Etiam sit amet rhoncus massa. Phasellus ligula magna, sollicitudin non imperdiet sit amet, volutpat vel magna. Nunc vestibulum tempor lectus, sit amet porta nunc hendrerit in. Curabitur non odio sit amet massa tincidunt facilisis. Integer et luctus nunc, eget euismod leo. Praesent faucibus metus sed purus convallis scelerisque. Fusce viverra lorem et placerat malesuada. In at elit malesuada, ullamcorper risus vitae, sodales dolor. Donec quis elementum lectus. Quisque eu eros at dui imperdiet euismod ut id neque.</text:p> <text:p text:style-name="P3">Nullam scelerisque velit et tortor congue vestibulum a at nisi. Vivamus sodales ut turpis a convallis. In dignissim nibh at luctus sodales. Etiam sit amet rhoncus massa. Phasellus ligula magna, sollicitudin non imperdiet sit amet, volutpat vel magna. Nunc vestibulum tempor lectus, sit amet porta nunc hendrerit in. Curabitur non odio sit amet massa tincidunt facilisis. Integer et luctus nunc, eget euismod leo. Praesent faucibus metus sed purus convallis scelerisque. Fusce viverra lorem et placerat malesuada. In at elit malesuada, ullamcorper risus vitae, sodales dolor. Donec quis elementum lectus. Quisque eu eros at dui imperdiet euismod ut id neque.</text:p>
<text:p text:style-name="P10">Notes: Plot</text:p> <text:p text:style-name="P10">Notes: Plot</text:p>
<text:h text:style-name="Heading_20_1" text:outline-level="1">Main Plot</text:h> <text:h text:style-name="Heading_20_1" text:outline-level="1">Main Plot</text:h>
<text:p text:style-name="Text_20_Meta"><text:span text:style-name="T7">Tag:</text:span> <text:span text:style-name="T8">Main</text:span></text:p> <text:p text:style-name="Text_20_Meta"><text:span text:style-name="T8">Tag:</text:span> <text:span text:style-name="T9">Main</text:span></text:p>
<text:p text:style-name="P6">Suspendisse vulputate malesuada pellentesque. Aenean sollicitudin cursus mi, vitae ultricies felis ullamcorper eu. Duis luctus risus mi, in accumsan velit cursus ut. Vestibulum eleifend leo in magna eleifend fermentum. Proin nec ornare elit. Phasellus nec interdum risus. In a volutpat augue, quis egestas justo. Morbi porta mauris mattis bibendum imperdiet.</text:p> <text:p text:style-name="P6">Suspendisse vulputate malesuada pellentesque. Aenean sollicitudin cursus mi, vitae ultricies felis ullamcorper eu. Duis luctus risus mi, in accumsan velit cursus ut. Vestibulum eleifend leo in magna eleifend fermentum. Proin nec ornare elit. Phasellus nec interdum risus. In a volutpat augue, quis egestas justo. Morbi porta mauris mattis bibendum imperdiet.</text:p>
<text:p text:style-name="P3">Mauris ut erat eu lorem malesuada egestas vel vel urna. Maecenas ac semper quam. Maecenas aliquet metus non interdum mattis. Proin consectetur molestie ligula. Aliquam sollicitudin pulvinar urna a pellentesque. Suspendisse ultrices, est mattis scelerisque porta, nisi nisi laoreet nisl, non condimentum quam ante a velit. Proin scelerisque justo augue, nec laoreet ligula egestas at. Etiam enim quam, ultrices non accumsan hendrerit, elementum vel ligula. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nam efficitur odio libero, in vestibulum arcu aliquam at. Cras non vehicula augue. Integer lobortis, est vitae aliquam facilisis, metus ligula aliquet eros, at porttitor sem tortor eget massa. Aliquam varius scelerisque neque sed gravida. Aenean eleifend lorem id ante elementum sollicitudin. Proin commodo massa a quam volutpat, mollis fermentum turpis efficitur.</text:p> <text:p text:style-name="P3">Mauris ut erat eu lorem malesuada egestas vel vel urna. Maecenas ac semper quam. Maecenas aliquet metus non interdum mattis. Proin consectetur molestie ligula. Aliquam sollicitudin pulvinar urna a pellentesque. Suspendisse ultrices, est mattis scelerisque porta, nisi nisi laoreet nisl, non condimentum quam ante a velit. Proin scelerisque justo augue, nec laoreet ligula egestas at. Etiam enim quam, ultrices non accumsan hendrerit, elementum vel ligula. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nam efficitur odio libero, in vestibulum arcu aliquam at. Cras non vehicula augue. Integer lobortis, est vitae aliquam facilisis, metus ligula aliquet eros, at porttitor sem tortor eget massa. Aliquam varius scelerisque neque sed gravida. Aenean eleifend lorem id ante elementum sollicitudin. Proin commodo massa a quam volutpat, mollis fermentum turpis efficitur.</text:p>
<text:p text:style-name="P10">Notes: World</text:p> <text:p text:style-name="P10">Notes: World</text:p>
<text:h text:style-name="Heading_20_1" text:outline-level="1">Ancient Europe</text:h> <text:h text:style-name="Heading_20_1" text:outline-level="1">Ancient Europe</text:h>
<text:p text:style-name="Text_20_Meta"><text:span text:style-name="T7">Tag:</text:span> <text:span text:style-name="T8">Europe</text:span> | <text:span text:style-name="T9">Ancient Europe</text:span></text:p> <text:p text:style-name="Text_20_Meta"><text:span text:style-name="T8">Tag:</text:span> <text:span text:style-name="T9">Europe</text:span> | <text:span text:style-name="T10">Ancient Europe</text:span></text:p>
<text:p text:style-name="P6">Vivamus sodales risus ac accumsan posuere. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nunc vel enim felis. Vestibulum dignissim massa nunc, a auctor magna eleifend et. Proin dignissim sodales erat vitae convallis. Aliquam id tellus dui. Curabitur sollicitudin scelerisque ex sit amet posuere. Nam rutrum felis id rhoncus feugiat. Duis sagittis quam quis purus efficitur, quis rutrum odio iaculis. Maecenas semper ante turpis, at vulputate mi consectetur non. Sed rutrum nibh turpis, quis rhoncus purus ornare quis. Vestibulum at rutrum mauris. Integer dolor nisi, tincidunt eget vehicula ac, ultricies at ligula.</text:p> <text:p text:style-name="P6">Vivamus sodales risus ac accumsan posuere. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nunc vel enim felis. Vestibulum dignissim massa nunc, a auctor magna eleifend et. Proin dignissim sodales erat vitae convallis. Aliquam id tellus dui. Curabitur sollicitudin scelerisque ex sit amet posuere. Nam rutrum felis id rhoncus feugiat. Duis sagittis quam quis purus efficitur, quis rutrum odio iaculis. Maecenas semper ante turpis, at vulputate mi consectetur non. Sed rutrum nibh turpis, quis rhoncus purus ornare quis. Vestibulum at rutrum mauris. Integer dolor nisi, tincidunt eget vehicula ac, ultricies at ligula.</text:p>
<text:p text:style-name="P3">Aenean semper turpis quis varius rhoncus. Vivamus ac mi eget felis euismod vulputate. Nam eu tempus velit. Etiam ut est porta, finibus erat sit amet, consectetur felis. Nullam consequat felis ut lacus pharetra, in lobortis urna mollis. Nulla varius eros nec lorem rhoncus, sed venenatis risus ultrices. Phasellus pellentesque laoreet neque, ut ultricies lacus vulputate quis. In malesuada dui sit amet est interdum, eget consectetur mi gravida. Cras vel bibendum purus. Quisque commodo tempor arcu, non lacinia sem blandit eleifend. Quisque at neque gravida, porttitor metus a, suscipit diam. Quisque convallis sodales lacus et condimentum. Donec a suscipit diam. Pellentesque eget cursus neque.</text:p> <text:p text:style-name="P3">Aenean semper turpis quis varius rhoncus. Vivamus ac mi eget felis euismod vulputate. Nam eu tempus velit. Etiam ut est porta, finibus erat sit amet, consectetur felis. Nullam consequat felis ut lacus pharetra, in lobortis urna mollis. Nulla varius eros nec lorem rhoncus, sed venenatis risus ultrices. Phasellus pellentesque laoreet neque, ut ultricies lacus vulputate quis. In malesuada dui sit amet est interdum, eget consectetur mi gravida. Cras vel bibendum purus. Quisque commodo tempor arcu, non lacinia sem blandit eleifend. Quisque at neque gravida, porttitor metus a, suscipit diam. Quisque convallis sodales lacus et condimentum. Donec a suscipit diam. Pellentesque eget cursus neque.</text:p>
<text:p text:style-name="P3">Nunc ullamcorper magna quis elit condimentum rhoncus. Aenean dictum pulvinar dolor suscipit interdum. Aliquam elit massa, elementum nec cursus eu, maximus nec ipsum. Donec ullamcorper iaculis dolor eu commodo. Nunc eget tortor quis turpis consectetur varius. Vestibulum nec justo vel tellus venenatis condimentum. Duis auctor iaculis massa. Nunc risus magna, rutrum vitae eros non, tristique mollis enim.</text:p> <text:p text:style-name="P3">Nunc ullamcorper magna quis elit condimentum rhoncus. Aenean dictum pulvinar dolor suscipit interdum. Aliquam elit massa, elementum nec cursus eu, maximus nec ipsum. Donec ullamcorper iaculis dolor eu commodo. Nunc eget tortor quis turpis consectetur varius. Vestibulum nec justo vel tellus venenatis condimentum. Duis auctor iaculis massa. Nunc risus magna, rutrum vitae eros non, tristique mollis enim.</text:p>
@@ -18,6 +18,8 @@ The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for t
_Lorem Ipsum_ is simply dummy text[1] of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum. _Lorem Ipsum_ is simply dummy text[1] of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
See http://lipsum.com
# Part: Act One # Part: Act One
“Fusce maximus felis libero” “Fusce maximus felis libero”
+46
View File
@@ -291,6 +291,48 @@ def testFmtToDocX_ParagraphStyles(mockGUI):
assert doc._pars == [] assert doc._pars == []
@pytest.mark.core
def testFmtToDocX_Links(mockGUI):
"""Test formatting of links."""
project = NWProject()
doc = ToDocX(project)
doc.initDocument()
# Register 2 links
rd1 = doc._appendExternalRel("http://example.com")
rd2 = doc._appendExternalRel("https://example.com")
assert rd1 == "rId1"
assert rd2 == "rId2"
# Link 1
xTest = ET.Element(_wTag("body"))
doc._text = "Foo http://example.com bar"
doc.tokenizeText()
doc.doConvert()
doc._pars[-1].toXml(xTest)
assert xmlToText(xTest) == (
'<w:body><w:p><w:pPr><w:pStyle w:val="Normal" /></w:pPr>'
'<w:r><w:rPr /><w:t xml:space="preserve">Foo </w:t></w:r>'
'<w:hyperlink r:id="rId1"><w:r><w:rPr><w:rStyle w:val="InternetLink" /></w:rPr>'
'<w:t>http://example.com</w:t></w:r></w:hyperlink>'
'<w:r><w:rPr /><w:t xml:space="preserve"> bar</w:t></w:r></w:p></w:body>'
)
# Link 2
xTest = ET.Element(_wTag("body"))
doc._text = "Foo https://example.com bar"
doc.tokenizeText()
doc.doConvert()
doc._pars[-1].toXml(xTest)
assert xmlToText(xTest) == (
'<w:body><w:p><w:pPr><w:pStyle w:val="Normal" /></w:pPr>'
'<w:r><w:rPr /><w:t xml:space="preserve">Foo </w:t></w:r>'
'<w:hyperlink r:id="rId2"><w:r><w:rPr><w:rStyle w:val="InternetLink" /></w:rPr>'
'<w:t>https://example.com</w:t></w:r></w:hyperlink>'
'<w:r><w:rPr /><w:t xml:space="preserve"> bar</w:t></w:r></w:p></w:body>'
)
@pytest.mark.core @pytest.mark.core
def testFmtToDocX_ParagraphFormatting(mockGUI): def testFmtToDocX_ParagraphFormatting(mockGUI):
"""Test formatting of paragraphs.""" """Test formatting of paragraphs."""
@@ -556,10 +598,13 @@ def testFmtToDocX_Footnotes(mockGUI):
assert xmlToText(doc._files["footnotes.xml"].xml) == ( assert xmlToText(doc._files["footnotes.xml"].xml) == (
'<w:footnotes>' '<w:footnotes>'
'<w:footnote w:id="1"><w:p><w:pPr><w:pStyle w:val="FootnoteText" /></w:pPr>' '<w:footnote w:id="1"><w:p><w:pPr><w:pStyle w:val="FootnoteText" /></w:pPr>'
'<w:r><w:rPr><w:vertAlign w:val="superscript" /></w:rPr><w:footnoteRef /></w:r>'
'<w:r><w:rPr /><w:t>Footnote text A.</w:t></w:r></w:p></w:footnote>' '<w:r><w:rPr /><w:t>Footnote text A.</w:t></w:r></w:p></w:footnote>'
'<w:footnote w:id="2"><w:p><w:pPr><w:pStyle w:val="FootnoteText" /></w:pPr>' '<w:footnote w:id="2"><w:p><w:pPr><w:pStyle w:val="FootnoteText" /></w:pPr>'
'<w:r><w:rPr><w:vertAlign w:val="superscript" /></w:rPr><w:footnoteRef /></w:r>'
'<w:r><w:rPr /><w:t>Another footnote.</w:t></w:r></w:p></w:footnote>' '<w:r><w:rPr /><w:t>Another footnote.</w:t></w:r></w:p></w:footnote>'
'<w:footnote w:id="3"><w:p><w:pPr><w:pStyle w:val="FootnoteText" /></w:pPr>' '<w:footnote w:id="3"><w:p><w:pPr><w:pStyle w:val="FootnoteText" /></w:pPr>'
'<w:r><w:rPr><w:vertAlign w:val="superscript" /></w:rPr><w:footnoteRef /></w:r>'
'<w:r><w:rPr /><w:t>Again?</w:t></w:r></w:p></w:footnote>' '<w:r><w:rPr /><w:t>Again?</w:t></w:r></w:p></w:footnote>'
'</w:footnotes>' '</w:footnotes>'
) )
@@ -619,6 +664,7 @@ def testFmtToDocX_SaveDocument(mockGUI, prjLipsum, fncPath, tstPaths):
fncPath / "extract" / "word" / "header2.xml", fncPath / "extract" / "word" / "header2.xml",
fncPath / "extract" / "word" / "settings.xml", fncPath / "extract" / "word" / "settings.xml",
fncPath / "extract" / "word" / "styles.xml", fncPath / "extract" / "word" / "styles.xml",
fncPath / "extract" / "word" / "fontTable.xml",
] ]
outDir = tstPaths.outDir / "fmtToDocX_SaveDocument" outDir = tstPaths.outDir / "fmtToDocX_SaveDocument"
+14 -14
View File
@@ -834,8 +834,8 @@ def testFmtToken_MetaFormat(mockGUI):
BlockTyp.KEYWORD, "char", "Characters: Bod", [ BlockTyp.KEYWORD, "char", "Characters: Bod", [
(0, TextFmt.B_B, ""), (0, TextFmt.COL_B, "keyword"), (0, TextFmt.B_B, ""), (0, TextFmt.COL_B, "keyword"),
(11, TextFmt.COL_E, ""), (11, TextFmt.B_E, ""), (11, TextFmt.COL_E, ""), (11, TextFmt.B_E, ""),
(12, TextFmt.COL_B, "tag"), (12, TextFmt.HRF_B, "#tag_bod"), (12, TextFmt.COL_B, "tag"), (12, TextFmt.ARF_B, "#tag_bod"),
(15, TextFmt.HRF_E, ""), (15, TextFmt.COL_E, ""), (15, TextFmt.ARF_E, ""), (15, TextFmt.COL_E, ""),
], BlockFmt.NONE ], BlockFmt.NONE
)] )]
assert tokens._raw[-1] == "@char: Bod\n\n" assert tokens._raw[-1] == "@char: Bod\n\n"
@@ -846,22 +846,22 @@ def testFmtToken_MetaFormat(mockGUI):
BlockTyp.KEYWORD, "pov", "Point of View: Bod", [ BlockTyp.KEYWORD, "pov", "Point of View: Bod", [
(0, TextFmt.B_B, ""), (0, TextFmt.COL_B, "keyword"), (0, TextFmt.B_B, ""), (0, TextFmt.COL_B, "keyword"),
(14, TextFmt.COL_E, ""), (14, TextFmt.B_E, ""), (14, TextFmt.COL_E, ""), (14, TextFmt.B_E, ""),
(15, TextFmt.COL_B, "tag"), (15, TextFmt.HRF_B, "#tag_bod"), (15, TextFmt.COL_B, "tag"), (15, TextFmt.ARF_B, "#tag_bod"),
(18, TextFmt.HRF_E, ""), (18, TextFmt.COL_E, ""), (18, TextFmt.ARF_E, ""), (18, TextFmt.COL_E, ""),
], BlockFmt.Z_BTM ], BlockFmt.Z_BTM
), ( ), (
BlockTyp.KEYWORD, "plot", "Plot: Main", [ BlockTyp.KEYWORD, "plot", "Plot: Main", [
(0, TextFmt.B_B, ""), (0, TextFmt.COL_B, "keyword"), (0, TextFmt.B_B, ""), (0, TextFmt.COL_B, "keyword"),
(5, TextFmt.COL_E, ""), (5, TextFmt.B_E, ""), (5, TextFmt.COL_E, ""), (5, TextFmt.B_E, ""),
(6, TextFmt.COL_B, "tag"), (6, TextFmt.HRF_B, "#tag_main"), (6, TextFmt.COL_B, "tag"), (6, TextFmt.ARF_B, "#tag_main"),
(10, TextFmt.HRF_E, ""), (10, TextFmt.COL_E, ""), (10, TextFmt.ARF_E, ""), (10, TextFmt.COL_E, ""),
], BlockFmt.Z_TOP | BlockFmt.Z_BTM ], BlockFmt.Z_TOP | BlockFmt.Z_BTM
), ( ), (
BlockTyp.KEYWORD, "location", "Locations: Europe", [ BlockTyp.KEYWORD, "location", "Locations: Europe", [
(0, TextFmt.B_B, ""), (0, TextFmt.COL_B, "keyword"), (0, TextFmt.B_B, ""), (0, TextFmt.COL_B, "keyword"),
(10, TextFmt.COL_E, ""), (10, TextFmt.B_E, ""), (10, TextFmt.COL_E, ""), (10, TextFmt.B_E, ""),
(11, TextFmt.COL_B, "tag"), (11, TextFmt.HRF_B, "#tag_europe"), (11, TextFmt.COL_B, "tag"), (11, TextFmt.ARF_B, "#tag_europe"),
(17, TextFmt.HRF_E, ""), (17, TextFmt.COL_E, ""), (17, TextFmt.ARF_E, ""), (17, TextFmt.COL_E, ""),
], BlockFmt.Z_TOP ], BlockFmt.Z_TOP
)] )]
assert tokens._raw[-1] == "@pov: Bod\n@plot: Main\n@location: Europe\n\n" assert tokens._raw[-1] == "@pov: Bod\n@plot: Main\n@location: Europe\n\n"
@@ -874,8 +874,8 @@ def testFmtToken_MetaFormat(mockGUI):
BlockTyp.KEYWORD, "pov", "Point of View: Bod", [ BlockTyp.KEYWORD, "pov", "Point of View: Bod", [
(0, TextFmt.B_B, ""), (0, TextFmt.COL_B, "keyword"), (0, TextFmt.B_B, ""), (0, TextFmt.COL_B, "keyword"),
(14, TextFmt.COL_E, ""), (14, TextFmt.B_E, ""), (14, TextFmt.COL_E, ""), (14, TextFmt.B_E, ""),
(15, TextFmt.COL_B, "tag"), (15, TextFmt.HRF_B, "#tag_bod"), (15, TextFmt.COL_B, "tag"), (15, TextFmt.ARF_B, "#tag_bod"),
(18, TextFmt.HRF_E, ""), (18, TextFmt.COL_E, ""), (18, TextFmt.ARF_E, ""), (18, TextFmt.COL_E, ""),
], BlockFmt.NONE ], BlockFmt.NONE
)] )]
@@ -1831,10 +1831,10 @@ def testFmtToken_FormatMeta(mockGUI):
"@char", "Characters: Jane, John", [ "@char", "Characters: Jane, John", [
(0, TextFmt.B_B, ""), (0, TextFmt.COL_B, "keyword"), (0, TextFmt.B_B, ""), (0, TextFmt.COL_B, "keyword"),
(11, TextFmt.COL_E, ""), (11, TextFmt.B_E, ""), (11, TextFmt.COL_E, ""), (11, TextFmt.B_E, ""),
(12, TextFmt.COL_B, "tag"), (12, TextFmt.HRF_B, "#tag_jane"), (12, TextFmt.COL_B, "tag"), (12, TextFmt.ARF_B, "#tag_jane"),
(16, TextFmt.HRF_E, ""), (16, TextFmt.COL_E, ""), (16, TextFmt.ARF_E, ""), (16, TextFmt.COL_E, ""),
(18, TextFmt.COL_B, "tag"), (18, TextFmt.HRF_B, "#tag_john"), (18, TextFmt.COL_B, "tag"), (18, TextFmt.ARF_B, "#tag_john"),
(22, TextFmt.HRF_E, ""), (22, TextFmt.COL_E, ""), (22, TextFmt.ARF_E, ""), (22, TextFmt.COL_E, ""),
] ]
) )
+22 -9
View File
@@ -38,13 +38,14 @@ from novelwriter.formats.toodt import ODTParagraphStyle, ODTTextStyle, ToOdt, XM
from tests.tools import ODT_IGNORE, cmpFiles from tests.tools import ODT_IGNORE, cmpFiles
XML_NS = [ XML_NS = [
' xmlns:dc="http://purl.org/dc/elements/1.1/"',
' xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0"',
' xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0"',
' xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0"',
' xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0"', ' xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0"',
' xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0"', ' xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0"',
' xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0"',
' xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0"', ' xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0"',
' xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0"', ' xmlns:xlink="http://www.w3.org/1999/xlink"',
' xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0"',
' xmlns:dc="http://purl.org/dc/elements/1.1/"'
] ]
@@ -1301,7 +1302,7 @@ def testFmtToOdt_XMLParagraph():
) )
# Text Span # Text Span
xmlPar.appendSpan("spanned text", "T1") xmlPar.appendSpan("spanned text", "T1", "")
assert xmlToText(xRoot) == ( assert xmlToText(xRoot) == (
'<root>' '<root>'
'<text:p>Hello World' '<text:p>Hello World'
@@ -1338,7 +1339,7 @@ def testFmtToOdt_XMLParagraph():
) )
# Text Span w/Line Break # Text Span w/Line Break
xmlPar.appendSpan("spanned\ntext", "T1") xmlPar.appendSpan("spanned\ntext", "T1", "")
assert xmlToText(xRoot) == ( assert xmlToText(xRoot) == (
'<root>' '<root>'
'<text:p>Hello<text:line-break />World<text:line-break />!!' '<text:p>Hello<text:line-break />World<text:line-break />!!'
@@ -1374,7 +1375,7 @@ def testFmtToOdt_XMLParagraph():
) )
# Text Span w/Line Break # Text Span w/Line Break
xmlPar.appendSpan("spanned\ttext", "T1") xmlPar.appendSpan("spanned\ttext", "T1", "")
assert xmlToText(xRoot) == ( assert xmlToText(xRoot) == (
'<root>' '<root>'
'<text:p>Hello<text:tab />World<text:tab />!!' '<text:p>Hello<text:tab />World<text:tab />!!'
@@ -1392,6 +1393,18 @@ def testFmtToOdt_XMLParagraph():
'</root>' '</root>'
) )
# Tail Text w/Link
xmlPar.appendSpan("Example", "T1", "http://www.example.com")
assert xmlToText(xRoot) == (
'<root>'
'<text:p>Hello<text:tab />World<text:tab />!!'
'<text:span text:style-name="T1">spanned<text:tab />text</text:span>'
'more<text:tab />text'
'<text:a xlink:type="simple" xlink:href="http://www.example.com" text:style-name="T1">'
'Example</text:a></text:p>'
'</root>'
)
assert xmlPar.checkError() == (0, "") assert xmlPar.checkError() == (0, "")
# Stage 4 : Spaces # Stage 4 : Spaces
@@ -1410,7 +1423,7 @@ def testFmtToOdt_XMLParagraph():
) )
# Text Span w/Spaces # Text Span w/Spaces
xmlPar.appendSpan("spanned text", "T1") xmlPar.appendSpan("spanned text", "T1", "")
assert xmlToText(xRoot) == ( assert xmlToText(xRoot) == (
'<root>' '<root>'
'<text:p>Hello <text:s />World <text:s text:c="2" />!!' '<text:p>Hello <text:s />World <text:s text:c="2" />!!'
@@ -1446,7 +1459,7 @@ def testFmtToOdt_XMLParagraph():
) )
# Text Span w/Many Spaces # Text Span w/Many Spaces
xmlPar.appendSpan(" C \t D \n E ", "T1") xmlPar.appendSpan(" C \t D \n E ", "T1", "")
assert xmlToText(xRoot) == ( assert xmlToText(xRoot) == (
'<root>' '<root>'
'<text:p><text:s text:c="2" /><text:tab /> A <text:line-break /> <text:s />B ' '<text:p><text:s text:c="2" /><text:tab /> A <text:line-break /> <text:s />B '
+14 -1
View File
@@ -464,10 +464,11 @@ def testFmtToQTextDocument_TextCharFormats(mockGUI):
"With sub[sub]script[/sub] text\n\n" "With sub[sub]script[/sub] text\n\n"
"With \u201cdialog\u201d text\n\n" "With \u201cdialog\u201d text\n\n"
"With |<alternative dialog>| text\n\n" "With |<alternative dialog>| text\n\n"
"With http://example.com text\n\n"
) )
doc.tokenizeText() doc.tokenizeText()
doc.doConvert() doc.doConvert()
assert doc.document.blockCount() == 10 assert doc.document.blockCount() == 11
# 0: Scene # 0: Scene
block = doc.document.findBlockByNumber(0) block = doc.document.findBlockByNumber(0)
@@ -563,6 +564,18 @@ def testFmtToQTextDocument_TextCharFormats(mockGUI):
cFmt = charFmtInBlock(block, 28) cFmt = charFmtInBlock(block, 28)
assert cFmt.foreground() == THEME.text assert cFmt.foreground() == THEME.text
# 10: Url
block = doc.document.findBlockByNumber(10)
assert block.text() == "With http://example.com text"
cFmt = charFmtInBlock(block, 1)
assert cFmt.foreground() == THEME.text
cFmt = charFmtInBlock(block, 6)
assert cFmt.foreground() == THEME.link
assert cFmt.isAnchor() is True
assert cFmt.anchorHref() == "http://example.com"
cFmt = charFmtInBlock(block, 24)
assert cFmt.foreground() == THEME.text
@pytest.mark.core @pytest.mark.core
def testFmtToQTextDocument_Footnotes(mockGUI): def testFmtToQTextDocument_Footnotes(mockGUI):
+45 -4
View File
@@ -20,10 +20,15 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
from __future__ import annotations from __future__ import annotations
from unittest.mock import MagicMock
import pytest import pytest
from PyQt5.QtCore import QEvent, Qt, QThreadPool from PyQt5.QtCore import QEvent, Qt, QThreadPool, QUrl
from PyQt5.QtGui import QClipboard, QFont, QMouseEvent, QTextBlock, QTextCursor, QTextOption from PyQt5.QtGui import (
QClipboard, QDesktopServices, QFont, QMouseEvent, QTextBlock, QTextCursor,
QTextOption
)
from PyQt5.QtWidgets import QAction, QApplication, QMenu from PyQt5.QtWidgets import QAction, QApplication, QMenu
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
@@ -267,8 +272,9 @@ def testGuiEditor_ContextMenu(monkeypatch, qtbot, nwGUI, projPath, mockRnd):
docText = ( docText = (
"### A Scene\n\n" "### A Scene\n\n"
"@pov: Jane\n" "@pov: Jane\n\n"
"Some text ..." "Some text ...\n\n"
"... and a link to http://example.com\n\n"
) )
docEditor.setPlainText(docText) docEditor.setPlainText(docText)
assert docEditor.getText() == docText assert docEditor.getText() == docText
@@ -289,6 +295,17 @@ def testGuiEditor_ContextMenu(monkeypatch, qtbot, nwGUI, projPath, mockRnd):
ctxMenu.setObjectName("") ctxMenu.setObjectName("")
ctxMenu.deleteLater() ctxMenu.deleteLater()
# Open Link
ctxMenu = getMenuForPos(docEditor, 63)
assert ctxMenu is not None
actions = [x.text() for x in ctxMenu.actions() if x.text()]
assert actions == [
"Open URL", "Paste",
"Select All", "Select Word", "Select Paragraph"
]
ctxMenu.setObjectName("")
ctxMenu.deleteLater()
# Create Character # Create Character
ctxMenu = getMenuForPos(docEditor, 21) ctxMenu = getMenuForPos(docEditor, 21)
assert ctxMenu is not None assert ctxMenu is not None
@@ -1656,6 +1673,30 @@ def testGuiEditor_Tags(qtbot, nwGUI, projPath, ipsumText, mockRnd):
# qtbot.stop() # qtbot.stop()
@pytest.mark.gui
def testGuiEditor_Links(qtbot, monkeypatch, nwGUI, projPath, ipsumText, mockRnd):
"""Test the document editor links functionality."""
buildTestProject(nwGUI, projPath)
nwGUI.openDocument(C.hSceneDoc)
docEditor = nwGUI.docEditor
docEditor.replaceText("### Scene\n\nFoo http://www.example.com bar.\n\n")
docEditor.setCursorPosition(20)
position = docEditor.cursorRect().center()
event = QMouseEvent(
QEvent.Type.MouseButtonPress, position, QtMouseLeft, QtMouseLeft, QtModCtrl
)
with monkeypatch.context() as mp:
openUrl = MagicMock()
mp.setattr(QDesktopServices, "openUrl", openUrl)
docEditor.mouseReleaseEvent(event)
assert openUrl.called is True
assert openUrl.call_args[0][0] == QUrl("http://www.example.com")
# qtbot.stop()
@pytest.mark.gui @pytest.mark.gui
def testGuiEditor_Completer(qtbot, nwGUI, projPath, mockRnd): def testGuiEditor_Completer(qtbot, nwGUI, projPath, mockRnd):
"""Test the document editor meta completer functionality.""" """Test the document editor meta completer functionality."""
+11 -1
View File
@@ -20,10 +20,12 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
from __future__ import annotations from __future__ import annotations
from unittest.mock import MagicMock
import pytest import pytest
from PyQt5.QtCore import QEvent, QPoint, Qt, QUrl from PyQt5.QtCore import QEvent, QPoint, Qt, QUrl
from PyQt5.QtGui import QMouseEvent, QTextCursor from PyQt5.QtGui import QDesktopServices, QMouseEvent, QTextCursor
from PyQt5.QtWidgets import QAction, QApplication, QMenu from PyQt5.QtWidgets import QAction, QApplication, QMenu
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
@@ -165,6 +167,14 @@ def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, prjLipsum):
docViewer._linkClicked(QUrl("#somewhere_else")) docViewer._linkClicked(QUrl("#somewhere_else"))
assert signal.args[0].url() == "#somewhere_else" assert signal.args[0].url() == "#somewhere_else"
# Web links should trigger the browser
with monkeypatch.context() as mp:
openUrl = MagicMock()
mp.setattr(QDesktopServices, "openUrl", openUrl)
docViewer._linkClicked(QUrl("http://www.example.com"))
assert openUrl.called is True
assert openUrl.call_args[0][0] == QUrl("http://www.example.com")
# Click mouse nav buttons # Click mouse nav buttons
qtbot.mouseClick(docViewer.viewport(), Qt.BackButton, pos=rect.center(), delay=100) qtbot.mouseClick(docViewer.viewport(), Qt.BackButton, pos=rect.center(), delay=100)
assert docViewer.docHandle == "88243afbe5ed8" assert docViewer.docHandle == "88243afbe5ed8"
+33 -1
View File
@@ -20,9 +20,12 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
from __future__ import annotations from __future__ import annotations
from unittest.mock import MagicMock
import pytest import pytest
from PyQt5.QtGui import QTextBlock, QTextCursor from PyQt5.QtCore import QUrl
from PyQt5.QtGui import QDesktopServices, QTextBlock, QTextCursor
from PyQt5.QtWidgets import QAction, QFileDialog, QMessageBox from PyQt5.QtWidgets import QAction, QFileDialog, QMessageBox
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
@@ -34,6 +37,35 @@ from novelwriter.types import QtKeepAnchor, QtMoveRight
from tests.tools import C, buildTestProject, writeFile from tests.tools import C, buildTestProject, writeFile
@pytest.mark.gui
def testGuiMainMenu_Slots(qtbot, monkeypatch, nwGUI, projPath):
"""Test the main menu slots."""
buildTestProject(nwGUI, projPath)
# Open URL
with monkeypatch.context() as mp:
openUrl = MagicMock()
mp.setattr(QDesktopServices, "openUrl", openUrl)
nwGUI.mainMenu._openWebsite("http://www.example.com")
assert openUrl.called is True
assert openUrl.call_args[0][0] == QUrl("http://www.example.com")
# Open Manual
with monkeypatch.context() as mp:
openUrl = MagicMock()
mp.setattr(QDesktopServices, "openUrl", openUrl)
CONFIG.pdfDocs = projPath / "manual.pdf"
CONFIG.pdfDocs.touch()
nwGUI.mainMenu._openUserManualFile()
assert openUrl.called is True
assert "manual.pdf" in openUrl.call_args[0][0].url()
# Spell Checking
assert SHARED.project.data.spellLang is None
nwGUI.mainMenu._changeSpelling("en")
assert SHARED.project.data.spellLang == "en"
@pytest.mark.gui @pytest.mark.gui
def testGuiMainMenu_EditFormat(qtbot, monkeypatch, nwGUI, prjLipsum): def testGuiMainMenu_EditFormat(qtbot, monkeypatch, nwGUI, prjLipsum):
"""Test the main menu Edit and Format entries.""" """Test the main menu Edit and Format entries."""
+46 -2
View File
@@ -40,6 +40,48 @@ def allMatches(regEx: re.Pattern, text: str) -> list[list[str]]:
return result return result
@pytest.mark.core
def testTextPatterns_Urls():
"""Test the URL regex."""
regEx = REGEX_PATTERNS.url
valid = [
"http://example.com",
"http://example.com/",
"http://example.com/path+to+page",
"http://example.com/path-to-page",
"http://example.com/path_to_page",
"http://example.com/path~to~page",
"http://example.com/path/to/page",
"http://example.com/path/to/page.html",
"http://example.com/path/to/page.html#title",
"http://example.com/path/to/page.html#title%20here",
"http://example.com/path/to/page.html#title%20here",
"http://example.com/path/to/page?foo=bar&bar=baz",
"http://example.com/path/to/page.html?foo=bar&bar=baz",
"http://example.com/path/to/page.html#title?foo=bar&bar=baz",
"http://user:password@example.com/",
"http://www.example.com/",
"http://www.www.example.com/",
"http://www.www.www.example.com/",
"https://example.com",
"https://www.example.com/",
]
invalid = [
"hppt://example.com/",
"sftp://example.com/",
"http:/example.com/",
"http://www example com/",
"http://www\texample\tcom/",
]
for test in valid:
assert allMatches(regEx, f"Text {test} more text") == [[(test, 5, 5 + len(test))]]
for test in invalid:
assert allMatches(regEx, f"Text {test} more text") == []
@pytest.mark.core @pytest.mark.core
def testTextPatterns_Words(): def testTextPatterns_Words():
"""Test the word split regex.""" """Test the word split regex."""
@@ -198,8 +240,10 @@ def testTextPatterns_ShortcodesPlain():
assert allMatches(regEx, "one [x]two[/x] three") == [] assert allMatches(regEx, "one [x]two[/x] three") == []
# Line Break Substitution
# ======================= @pytest.mark.core
def testTextPatterns_LineBreakReplace():
"""Test replacing forced line breaks."""
regEx = REGEX_PATTERNS.lineBreak regEx = REGEX_PATTERNS.lineBreak
assert regEx.sub("\n", "one[br]two") == "one\ntwo" assert regEx.sub("\n", "one[br]two") == "one\ntwo"
+18 -2
View File
@@ -22,9 +22,12 @@ from __future__ import annotations
import sys import sys
from unittest.mock import MagicMock
import pytest import pytest
from PyQt5.QtCore import pyqtSlot from PyQt5.QtCore import QUrl, pyqtSlot
from PyQt5.QtGui import QDesktopServices
from PyQt5.QtPrintSupport import QPrintPreviewDialog from PyQt5.QtPrintSupport import QPrintPreviewDialog
from PyQt5.QtWidgets import QAction, QListWidgetItem from PyQt5.QtWidgets import QAction, QListWidgetItem
@@ -225,13 +228,26 @@ def testToolManuscript_Features(monkeypatch, qtbot, nwGUI, projPath, mockRnd):
assert (item := listView.topLevelItem(3)) and item.data(0, keyRole) == "000000000000c:T0006" assert (item := listView.topLevelItem(3)) and item.data(0, keyRole) == "000000000000c:T0006"
assert (item := listView.topLevelItem(4)) and item.data(0, keyRole) == "000000000000e:T0001" assert (item := listView.topLevelItem(4)) and item.data(0, keyRole) == "000000000000e:T0001"
# Click Outline # Click outline
item = listView.topLevelItem(4) item = listView.topLevelItem(4)
assert item is not None assert item is not None
with qtbot.waitSignal(manus.buildOutline.outlineEntryClicked) as signal: with qtbot.waitSignal(manus.buildOutline.outlineEntryClicked) as signal:
manus.buildOutline._onItemClick(item) manus.buildOutline._onItemClick(item)
assert signal.args == ["000000000000e:T0001"] assert signal.args == ["000000000000e:T0001"]
# Preview Navigation
assert manus.docPreview.source() == QUrl("#000000000000e:T0001")
manus.docPreview.navigateTo("000000000000c:T0002")
assert manus.docPreview.source() == QUrl("#000000000000c:T0002")
manus.docPreview._linkClicked(QUrl("#000000000000c:T0003"))
assert manus.docPreview.source() == QUrl("#000000000000c:T0003")
with monkeypatch.context() as mp:
openUrl = MagicMock()
mp.setattr(QDesktopServices, "openUrl", openUrl)
manus.docPreview._linkClicked(QUrl("http://www.example.com"))
assert openUrl.called is True
assert openUrl.call_args[0][0] == QUrl("http://www.example.com")
# Check Preview Stats # Check Preview Stats
assert manus.docStats.mainStack.currentWidget() == manus.docStats.minWidget assert manus.docStats.mainStack.currentWidget() == manus.docStats.minWidget
assert manus.docStats.minWordCount.text() == "25" assert manus.docStats.minWordCount.text() == "25"
+6 -6
View File
@@ -60,15 +60,15 @@ def testToolNovelDetails_Main(qtbot, nwGUI, prjLipsum, ipsumText):
# Check project data # Check project data
assert overview.projName.text() == "Lorem Ipsum" assert overview.projName.text() == "Lorem Ipsum"
assert overview.projWords.text() == f"{4376:n}" assert overview.projWords.text() == f"{4378:n}"
assert overview.projNovels.text() == f"{3638:n}" assert overview.projNovels.text() == f"{3640:n}"
assert overview.projNotes.text() == f"{738:n}" assert overview.projNotes.text() == f"{738:n}"
assert overview.projRevisions.text() != "" assert overview.projRevisions.text() != ""
assert overview.projEditTime.text() != "" assert overview.projEditTime.text() != ""
# Check novel data for "Novel" # Check novel data for "Novel"
assert overview.novelName.text() == "Novel" assert overview.novelName.text() == "Novel"
assert overview.novelWords.text() == f"{3000:n}" assert overview.novelWords.text() == f"{3002:n}"
assert overview.novelChapters.text() == f"{3:n}" assert overview.novelChapters.text() == f"{3:n}"
assert overview.novelScenes.text() == f"{5:n}" assert overview.novelScenes.text() == f"{5:n}"
@@ -87,7 +87,7 @@ def testToolNovelDetails_Main(qtbot, nwGUI, prjLipsum, ipsumText):
contents = details.contentsPage contents = details.contentsPage
# Check defaults # Check defaults
words = [f"{v:n}" for v in [40, 176, 92, 6, 1071, 1615, 0]] words = [f"{v:n}" for v in [40, 176, 94, 6, 1071, 1615, 0]]
pages = [f"{v:n}" for v in [2, 2, 2, 2, 4, 6, 0]] pages = [f"{v:n}" for v in [2, 2, 2, 2, 4, 6, 0]]
page = [f"{v:n}" for v in [1, 3, 5, 7, 9, 13, 19]] page = [f"{v:n}" for v in [1, 3, 5, 7, 9, 13, 19]]
for i in range(6): for i in range(6):
@@ -100,7 +100,7 @@ def testToolNovelDetails_Main(qtbot, nwGUI, prjLipsum, ipsumText):
# Change Settings # Change Settings
contents.poValue.setValue(7) contents.poValue.setValue(7)
contents.wpValue.setValue(50) contents.wpValue.setValue(50)
words = [f"{v:n}" for v in [40, 176, 92, 6, 1071, 1615, 0]] words = [f"{v:n}" for v in [40, 176, 94, 6, 1071, 1615, 0]]
pages = [f"{v:n}" for v in [2, 4, 2, 2, 22, 34, 0]] pages = [f"{v:n}" for v in [2, 4, 2, 2, 22, 34, 0]]
page = ["i", "iii"] + [f"{v:n}" for v in [1, 3, 5, 27, 61]] page = ["i", "iii"] + [f"{v:n}" for v in [1, 3, 5, 27, 61]]
for i in range(6): for i in range(6):
@@ -114,7 +114,7 @@ def testToolNovelDetails_Main(qtbot, nwGUI, prjLipsum, ipsumText):
contents.dblValue.setChecked(False) contents.dblValue.setChecked(False)
contents.poValue.setValue(0) contents.poValue.setValue(0)
contents.wpValue.setValue(100) contents.wpValue.setValue(100)
words = [f"{v:n}" for v in [40, 176, 92, 6, 1071, 1615, 0]] words = [f"{v:n}" for v in [40, 176, 94, 6, 1071, 1615, 0]]
pages = [f"{v:n}" for v in [1, 2, 1, 1, 11, 17, 0]] pages = [f"{v:n}" for v in [1, 2, 1, 1, 11, 17, 0]]
page = [f"{v:n}" for v in [1, 2, 4, 5, 6, 17, 34]] page = [f"{v:n}" for v in [1, 2, 4, 5, 6, 17, 34]]
for i in range(6): for i in range(6):