Skip to content

Commit

Permalink
Merge pull request #547 from ColonelThirtyTwo/table-captions
Browse files Browse the repository at this point in the history
Add captions to tables
  • Loading branch information
chrismaddalena authored Nov 14, 2024
2 parents c632a60 + 6721680 commit 4fb9dfc
Show file tree
Hide file tree
Showing 10 changed files with 144 additions and 22 deletions.
2 changes: 2 additions & 0 deletions ghostwriter/commandcenter/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ class ReportConfigurationAdmin(SingletonModelAdmin):
"fields": (
"prefix_figure",
"label_figure",
"figure_caption_location",
)
},
),
Expand All @@ -65,6 +66,7 @@ class ReportConfigurationAdmin(SingletonModelAdmin):
"fields": (
"prefix_table",
"label_table",
"table_caption_location",
)
},
),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 3.2.19 on 2024-11-08 18:54

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('commandcenter', '0030_alter_extrafieldspec_type'),
]

operations = [
migrations.AddField(
model_name='reportconfiguration',
name='table_caption_location',
field=models.CharField(choices=[('top', 'Top'), ('bottom', 'Bottom')], default='top', help_text='Where to place table captions relative to the table', max_length=10, verbose_name='Table Caption Location'),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 3.2.19 on 2024-11-12 15:47

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('commandcenter', '0031_reportconfiguration_table_caption_location'),
]

operations = [
migrations.AddField(
model_name='reportconfiguration',
name='figure_caption_location',
field=models.CharField(choices=[('top', 'Top'), ('bottom', 'Bottom')], default='bottom', help_text='Where to place figure captions relative to the figure', max_length=10, verbose_name='Figure Caption Location'),
),
]
14 changes: 14 additions & 0 deletions ghostwriter/commandcenter/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,13 @@ class ReportConfiguration(SingletonModel):
default="Figure",
help_text="The label that comes before the figure number and caption in Word reports",
)
figure_caption_location = models.CharField(
"Figure Caption Location",
max_length=10,
choices=[("top", "Top"), ("bottom", "Bottom")],
default="bottom",
help_text="Where to place figure captions relative to the figure",
)
prefix_table = models.CharField(
"Character Before Table Titles",
max_length=255,
Expand All @@ -107,6 +114,13 @@ class ReportConfiguration(SingletonModel):
default="Table",
help_text="The label that comes before the table number and caption in Word reports",
)
table_caption_location = models.CharField(
"Table Caption Location",
max_length=10,
choices=[("top", "Top"), ("bottom", "Bottom")],
default="top",
help_text="Where to place table captions relative to the table",
)
report_filename = models.CharField(
"Default Name for Report Downloads",
max_length=255,
Expand Down
4 changes: 4 additions & 0 deletions ghostwriter/commandcenter/tests/test_forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,10 @@ def form_data(
border_color=None,
prefix_figure=None,
label_figure=None,
figure_caption_location=None,
prefix_table=None,
label_table=None,
table_caption_location=None,
report_filename=None,
project_filename=None,
default_docx_template_id=None,
Expand All @@ -65,8 +67,10 @@ def form_data(
"border_color": border_color,
"prefix_figure": prefix_figure,
"label_figure": label_figure,
"figure_caption_location": figure_caption_location,
"prefix_table": prefix_table,
"label_table": label_table,
"table_caption_location": table_caption_location,
"report_filename": report_filename,
"project_filename": project_filename,
"default_docx_template": default_docx_template_id,
Expand Down
8 changes: 8 additions & 0 deletions ghostwriter/home/templates/home/management.html
Original file line number Diff line number Diff line change
Expand Up @@ -204,10 +204,18 @@ <h2>API & Notification Configurations</h2>
<td class="text-left icon tag-icon">Figure Label & Prefix</td>
<td class="text-justify">{{ report_config.label_figure }}{{ report_config.prefix_figure }}</td>
</tr>
<tr>
<td class="text-left icon tag-icon">Figure Caption Location</td>
<td class="text-justify">{{ report_config.get_figure_caption_location_display }}</td>
</tr>
<tr>
<td class="text-left icon tag-icon">Table Label & Prefix</td>
<td class="text-justify">{{ report_config.label_table }}{{ report_config.prefix_table }}</td>
</tr>
<tr>
<td class="text-left icon tag-icon">Table Caption Location</td>
<td class="text-justify">{{ report_config.get_table_caption_location_display }}</td>
</tr>
<tr>
<td class="text-left icon tag-icon">Title Case Captions</td>
<td class="text-justify"> {{ report_config.title_case_captions }}</td>
Expand Down
10 changes: 10 additions & 0 deletions ghostwriter/modules/reportwriter/base/docx.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@ def __init__(self, object, *, template_loc: str, p_style: str | None, linting: b
self.prefix_figure = f"{prefix_figure}"
label_figure = global_report_config.label_figure
self.label_figure = f"{label_figure}"
prefix_table = global_report_config.prefix_table
self.prefix_table = f"{prefix_table}"
self.figure_caption_location = global_report_config.figure_caption_location
label_table = global_report_config.label_table
self.label_table = f"{label_table}"
self.table_caption_location = global_report_config.table_caption_location
self.title_case_captions = global_report_config.title_case_captions
self.title_case_exceptions = global_report_config.title_case_exceptions.split(",")

Expand Down Expand Up @@ -231,6 +237,10 @@ def render_rich_text_docx(self, rich_text: LazilyRenderedTemplate):
evidences=self.evidences_by_id,
figure_label=self.label_figure,
figure_prefix=self.prefix_figure,
figure_caption_location=self.figure_caption_location,
table_label=self.label_table,
table_prefix=self.prefix_table,
table_caption_location=self.table_caption_location,
title_case_captions=self.title_case_captions,
title_case_exceptions=self.title_case_exceptions,
border_color_width=(self.border_color, self.border_weight) if self.enable_borders else None,
Expand Down
80 changes: 60 additions & 20 deletions ghostwriter/modules/reportwriter/richtext/docx.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,6 @@ def tag_blockquote(self, el, par=None, **kwargs):
def create_table(self, rows, cols, **kwargs):
table = self.doc.add_table(rows=rows, cols=cols, style="Table Grid")
self.set_autofit()

return table

def paragraph_for_table_cell(self, cell, td_el):
Expand Down Expand Up @@ -272,10 +271,15 @@ class HtmlToDocxWithEvidence(HtmlToDocx):
def __init__(
self,
doc,
*,
p_style,
evidences,
figure_label: str,
figure_prefix: str,
figure_caption_location: str,
table_label: str,
table_prefix: str,
table_caption_location: str,
title_case_captions: bool,
title_case_exceptions: list[str],
border_color_width: tuple[str, float] | None,
Expand All @@ -284,6 +288,10 @@ def __init__(
self.evidences = evidences
self.figure_label = figure_label
self.figure_prefix = figure_prefix
self.figure_caption_location = figure_caption_location
self.table_label = table_label
self.table_prefix = table_prefix
self.table_caption_location = table_caption_location
self.title_case_captions = title_case_captions
self.title_case_exceptions = title_case_exceptions
self.border_color_width = border_color_width
Expand All @@ -303,19 +311,33 @@ def tag_span(self, el, *, par, **kwargs):
self.make_evidence(par, evidence)
elif "data-gw-caption" in el.attrs:
ref_name = el.attrs["data-gw-caption"]
try:
par.style = "Caption"
except KeyError:
pass
par._gw_is_caption = True
self.make_figure(par, ref_name or None)
self.make_caption(par, self.figure_label, ref_name or None)
par.add_run(self.figure_prefix)
elif "data-gw-ref" in el.attrs:
ref_name = el.attrs["data-gw-ref"]
self.text_tracking.force_emit_pending_segment_break()
self.make_cross_ref(par, ref_name)
else:
super().tag_span(el, par=par, **kwargs)

def tag_table(self, el, **kwargs):
if self.table_caption_location == "top":
self._mk_table_caption(el)
super().tag_table(el, **kwargs)
if self.table_caption_location == "bottom":
self._mk_table_caption(el)

def _mk_table_caption(self, el):
par_caption = self.doc.add_paragraph()
self.make_caption(par_caption, self.table_label, None)
caption = next((child for child in el.children if child.name == "caption"), None)
if caption is not None:
par_caption.add_run(self.table_prefix)
par_caption.add_run(self.title_except(caption.get_text()))

if self.table_caption_location == "top":
super().tag_table(el, **kwargs)

def is_plural_acronym(self, word):
"""
Check if a word is an all caps acronym that ends with "s" or "'s".
Expand All @@ -340,7 +362,13 @@ def title_except(self, s):
s = " ".join(final)
return s

def make_figure(self, par, ref: str | None = None):
def make_caption(self, par, label: str, ref: str | None = None):
par._gw_is_caption = True
try:
par.style = "Caption"
except KeyError:
pass

if ref:
ref = f"_Ref{ref}"
else:
Expand All @@ -354,7 +382,7 @@ def make_figure(self, par, ref: str | None = None):
p.append(bookmark_start)

# Add the figure label
run = par.add_run(self.figure_label)
par.add_run(label)

# Append XML for a new field character run
run = par.add_run()
Expand All @@ -368,7 +396,7 @@ def make_figure(self, par, ref: str | None = None):
r = run._r
instrText = OxmlElement("w:instrText")
# Sequential figure with arabic numbers
instrText.text = " SEQ Figure \\* ARABIC"
instrText.text = f" SEQ {label} \\* ARABIC"
r.append(instrText)

# An optional ``separate`` value to enforce a space between label and number
Expand All @@ -392,9 +420,6 @@ def make_figure(self, par, ref: str | None = None):
bookmark_end.set(qn("w:id"), "0")
p.append(bookmark_end)

# Add prefix
par.add_run(self.figure_prefix)

def make_evidence(self, par, evidence):
file_path = settings.MEDIA_ROOT + "/" + evidence["path"]
if not os.path.exists(file_path):
Expand All @@ -417,16 +442,27 @@ def make_evidence(self, par, evidence):
"Try opening it, exporting as desired type, and re-uploading it."
)
raise ReportExportError(error_msg) from err

if self.figure_caption_location == "top":
self._mk_figure_caption(par, evidence["friendly_name"], evidence["caption"])
par = self.doc.add_paragraph()

par.text = evidence_text
par.alignment = WD_ALIGN_PARAGRAPH.LEFT
try:
par.style = "CodeBlock"
except KeyError:
pass
par_caption = self.doc.add_paragraph(style="Caption")
self.make_figure(par_caption, evidence["friendly_name"])
par_caption.add_run(self.title_except(evidence["caption"]))

if self.figure_caption_location == "bottom":
par_caption = self.doc.add_paragraph()
self._mk_figure_caption(par_caption, evidence["friendly_name"], evidence["caption"])

elif extension in IMAGE_EXTENSIONS:
if self.figure_caption_location == "top":
self._mk_figure_caption(par, evidence["friendly_name"], evidence["caption"])
par = self.doc.add_paragraph()

par.alignment = WD_ALIGN_PARAGRAPH.CENTER
run = par.add_run()
try:
Expand Down Expand Up @@ -474,10 +510,14 @@ def make_evidence(self, par, evidence):
ln_xml.append(solidfill_xml)
pic_data.append(ln_xml)

# Create the caption for the image
p = self.doc.add_paragraph(style="Caption")
self.make_figure(p, evidence["friendly_name"])
run = p.add_run(self.title_except(evidence["caption"]))
if self.figure_caption_location == "bottom":
par_caption = self.doc.add_paragraph()
self._mk_figure_caption(par_caption, evidence["friendly_name"], evidence["caption"])

def _mk_figure_caption(self, par_caption, ref: str | None, caption_text: str):
self.make_caption(par_caption, self.figure_label, ref)
par_caption.add_run(self.figure_prefix)
par_caption.add_run(self.title_except(caption_text))

def make_cross_ref(self, par, ref: str):
# Start the field character run for the label and number
Expand Down
4 changes: 2 additions & 2 deletions ghostwriter/modules/reportwriter/richtext/ooxml.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ def handle_style(key, value):
self.process_children(el.children, style=style, **kwargs)

def tag_table(self, el, **kwargs):
self.text_tracking.new_block()
table_width, table_height = self._table_size(el)
ooxml_table = self.create_table(rows=table_height, cols=table_width, **kwargs)

Expand Down Expand Up @@ -243,8 +244,7 @@ def _table_rows(table_el):
for item in table_el.children:
if item.name == "tr":
yield item
elif item.name is not None:
# thead, tbody, tfoot
elif item.name in ("thead", "tbody", "tfoot"):
for subitem in item:
if subitem.name == "tr":
yield subitem
Expand Down
8 changes: 8 additions & 0 deletions ghostwriter/reporting/templates/reporting/report_detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -132,10 +132,18 @@ <h4>Report Configuration</h4>
<td class="text-left icon tag-icon">Figure Label & Prefix</td>
<td class="text-justify">{{ report_config.label_figure }}{{ report_config.prefix_figure }}</td>
</tr>
<tr>
<td class="text-left icon tag-icon">Figure Caption Location</td>
<td class="text-justify">{{ report_config.get_figure_caption_location_display }}</td>
</tr>
<tr>
<td class="text-left icon tag-icon">Table Label & Prefix</td>
<td class="text-justify">{{ report_config.label_table }}{{ report_config.prefix_table }}</td>
</tr>
<tr>
<td class="text-left icon tag-icon">Table Caption Location</td>
<td class="text-justify">{{ report_config.get_table_caption_location_display }}</td>
</tr>
<tr>
<td class="text-left icon tag-icon">Title Case Captions</td>
<td class="text-justify">
Expand Down

0 comments on commit 4fb9dfc

Please sign in to comment.