aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--screenplain/export/fdx.py80
-rw-r--r--screenplain/main.py8
-rw-r--r--tests/fdx_test.py45
3 files changed, 132 insertions, 1 deletions
diff --git a/screenplain/export/fdx.py b/screenplain/export/fdx.py
new file mode 100644
index 0000000..32e18f5
--- /dev/null
+++ b/screenplain/export/fdx.py
@@ -0,0 +1,80 @@
+from xml.sax.saxutils import escape
+
+from screenplain.types import *
+from screenplain.richstring import RichString, Bold, Italic, Underline
+
+paragraph_types = {
+ Slug: 'Scene Heading',
+ Action: 'Action',
+ Transition: 'Transition',
+}
+
+style_names = {
+ Bold: 'Bold',
+ Italic: 'Italic',
+ Underline: 'Underline',
+ RichString: None,
+}
+
+
+def _write_text_element(out, styles, text):
+ style_value = '+'.join(str(s) for s in styles)
+ if style_value == '':
+ out.write('<Text>%s</Text>' % (escape(text)))
+ else:
+ out.write('<Text style="%s">%s</Text>' % (style_value, escape(text)))
+
+
+def write_text(out, rich, parent_styles=None):
+ parent_styles = parent_styles or set()
+ style_name = style_names[type(rich)]
+ if style_name:
+ styles = parent_styles | set((style_name,))
+ else:
+ styles = parent_styles
+
+ for segment in rich.segments:
+ if isinstance(segment, basestring):
+ _write_text_element(out, styles, segment)
+ else:
+ write_text(out, segment, styles)
+
+
+def write_paragraph(out, para_type, lines):
+ out.write('<Paragraph Type="%s">' % para_type)
+ for line in lines:
+ write_text(out, line)
+ out.write('</Paragraph>')
+
+
+def write_dialog(out, dialog):
+ write_paragraph(out, 'Character', [dialog.character])
+ for parenthetical, line in dialog.blocks:
+ if parenthetical:
+ write_paragraph(out, 'Parenthetical', [line])
+ else:
+ write_paragraph(out, 'Dialogue', [line])
+
+def write_dual_dialog(out, dual):
+ out.write('<Paragraph><DualDialogue>')
+ write_dialog(out, dual.left)
+ write_dialog(out, dual.right)
+ out.write('</DualDialogue></Paragraph>')
+
+def to_fdx(screenplay, out):
+
+ out.write(
+ '<?xml version="1.0" encoding="UTF-8" standalone="no" ?>'
+ '<FinalDraft DocumentType="Script" Template="No" Version="1">'
+ '<Content>'
+ )
+
+ for para in screenplay:
+ if isinstance(para, Dialog):
+ write_dialog(out, para)
+ elif isinstance(para, DualDialog):
+ write_dual_dialog(out, para)
+ else:
+ para_type = paragraph_types[type(para)]
+ write_paragraph(out, para_type, para.lines)
+ out.write('</Content></FinalDraft>\n')
diff --git a/screenplain/main.py b/screenplain/main.py
index 2c114a6..43b48d5 100644
--- a/screenplain/main.py
+++ b/screenplain/main.py
@@ -11,7 +11,7 @@ from optparse import OptionParser
from screenplain.parsers.spmd import parse
output_formats = (
- 'text', 'pdf', 'annotated_html'
+ 'text', 'pdf', 'fdx', 'annotated_html'
)
usage = 'Usage: %prog [options] input-file output-file'
@@ -33,8 +33,11 @@ def main(args):
if options.output_format == None:
if output_file.endswith('.pdf'):
options.output_format = 'pdf'
+ elif output_file.endswith('.fdx'):
+ options.output_format = 'fdx'
else:
options.output_format = 'text'
+
if options.output_format not in output_formats:
parser.error(
'Unknown output format. Expected one of: ' +
@@ -52,6 +55,9 @@ def main(args):
if options.output_format == 'text':
from screenplain.export.text import to_text
to_text(screenplay, output)
+ elif options.output_format == 'fdx':
+ from screenplain.export.fdx import to_fdx
+ to_fdx(screenplay, output)
elif options.output_format == 'annotated_html':
from screenplain.export.annotated_html import to_annotated_html
to_annotated_html(screenplay, output)
diff --git a/tests/fdx_test.py b/tests/fdx_test.py
new file mode 100644
index 0000000..9737493
--- /dev/null
+++ b/tests/fdx_test.py
@@ -0,0 +1,45 @@
+import unittest2
+from StringIO import StringIO
+
+from screenplain.export.fdx import write_text
+
+from screenplain.richstring import RichString, Bold, Italic
+
+class OutputTests(unittest2.TestCase):
+
+ def setUp(self):
+ self.out = StringIO()
+
+ def test_plain_text_should_have_no_style(self):
+ write_text(self.out, RichString('hello'))
+ self.assertEqual(
+ self.out.getvalue(),
+ '<Text>hello</Text>'
+ )
+
+ def test_bold_text_should_have_bold_style(self):
+ write_text(self.out, Bold('hello'))
+ self.assertEqual(
+ self.out.getvalue(),
+ '<Text style="Bold">hello</Text>'
+ )
+
+ def test_sequential_styles(self):
+ rich = RichString('plain', Bold('b'), Italic('i'))
+ write_text(self.out, rich)
+ self.assertEqual(
+ self.out.getvalue(),
+ '<Text>plain</Text>'
+ '<Text style="Bold">b</Text>'
+ '<Text style="Italic">i</Text>'
+ )
+
+ def test_nested_styles(self):
+ rich = Bold('outer', Italic('inner'), 'outer')
+ write_text(self.out, rich)
+ self.assertEqual(
+ self.out.getvalue(),
+ '<Text style="Bold">outer</Text>'
+ '<Text style="Bold+Italic">inner</Text>'
+ '<Text style="Bold">outer</Text>'
+ )