summaryrefslogtreecommitdiff
path: root/mammoth
diff options
context:
space:
mode:
authorMichael Williamson <mike@zwobble.org>2020-04-12 14:58:34 +0100
committerMichael Williamson <mike@zwobble.org>2020-04-12 14:58:34 +0100
commit7a9d6f05dbf51db840ab010172f4ed3d6fca6257 (patch)
tree85560bad7fddfd99a09a47932f85f0b42241dbae /mammoth
parent6f7852e45a08c60bdb5961c7a43b1b5bc41d3b10 (diff)
Read font size for runs
Diffstat (limited to 'mammoth')
-rw-r--r--mammoth/documents.py19
-rw-r--r--mammoth/docx/body_xml.py168
2 files changed, 99 insertions, 88 deletions
diff --git a/mammoth/documents.py b/mammoth/documents.py
index b00f1bb..e1e1e58 100644
--- a/mammoth/documents.py
+++ b/mammoth/documents.py
@@ -30,7 +30,7 @@ class ParagraphIndent(object):
end = cobble.field()
first_line = cobble.field()
hanging = cobble.field()
-
+
@cobble.data
class Indent(object):
@@ -51,6 +51,7 @@ class Run(HasChildren):
is_small_caps = cobble.field()
vertical_alignment = cobble.field()
font = cobble.field()
+ font_size = cobble.field()
@cobble.data
class Text(Element):
@@ -88,7 +89,7 @@ column_break = Break("column")
@cobble.data
class Tab(Element):
pass
-
+
@cobble.data
class Image(Element):
@@ -107,7 +108,7 @@ def document(children, notes=None, comments=None):
def paragraph(children, style_id=None, style_name=None, numbering=None, alignment=None, indent=None):
if indent is None:
indent = paragraph_indent()
-
+
return Paragraph(children, style_id, style_name, numbering, alignment=alignment, indent=indent)
def paragraph_indent(start=None, end=None, first_line=None, hanging=None):
@@ -124,6 +125,7 @@ def run(
is_small_caps=None,
vertical_alignment=None,
font=None,
+ font_size=None,
):
if vertical_alignment is None:
vertical_alignment = VerticalAlignment.baseline
@@ -138,6 +140,7 @@ def run(
is_small_caps=bool(is_small_caps),
vertical_alignment=vertical_alignment,
font=font,
+ font_size=font_size,
)
class VerticalAlignment(object):
@@ -164,7 +167,7 @@ class Bookmark(Element):
name = cobble.field()
bookmark = Bookmark
-
+
def table(children, style_id=None, style_name=None):
return Table(children=children, style_id=style_id, style_name=style_name)
@@ -201,13 +204,13 @@ note = Note
class Notes(object):
def __init__(self, notes):
self._notes = notes
-
+
def find_note(self, note_type, note_id):
return self._notes[(note_type, note_id)]
-
+
def resolve(self, reference):
return self.find_note(reference.note_type, reference.note_id)
-
+
def __eq__(self, other):
return isinstance(other, Notes) and self._notes == other._notes
@@ -219,7 +222,7 @@ def notes(notes_list):
(_note_key(note), note)
for note in notes_list
))
-
+
def _note_key(note):
return (note.note_type, note.note_id)
diff --git a/mammoth/docx/body_xml.py b/mammoth/docx/body_xml.py
index d6ca993..7bb8628 100644
--- a/mammoth/docx/body_xml.py
+++ b/mammoth/docx/body_xml.py
@@ -22,10 +22,10 @@ def reader(
docx_file=None,
files=None
):
-
+
if styles is None:
styles = Styles.EMPTY
-
+
read_all = _create_reader(
numbering=numbering,
content_types=content_types,
@@ -41,7 +41,7 @@ def reader(
class _BodyReader(object):
def __init__(self, read_all):
self._read_all = read_all
-
+
def read_all(self, elements):
result = self._read_all(elements)
return results.Result(result.elements, result.messages)
@@ -79,20 +79,27 @@ def _create_reader(numbering, content_types, relationships, styles, docx_file, f
.find_child_or_null("w:vertAlign") \
.attributes.get("w:val")
font = properties.find_child_or_null("w:rFonts").attributes.get("w:ascii")
-
+
+ font_size_string = properties.find_child_or_null("w:sz").attributes.get("w:val")
+ if font_size_string:
+ # w:sz gives the font size in half points, so halve the value to get the size in points
+ font_size = int(font_size_string) / 2
+ else:
+ font_size = None
+
is_bold = read_boolean_element(properties.find_child("w:b"))
is_italic = read_boolean_element(properties.find_child("w:i"))
is_underline = read_boolean_element(properties.find_child("w:u"))
is_strikethrough = read_boolean_element(properties.find_child("w:strike"))
is_small_caps = read_boolean_element(properties.find_child("w:smallCaps"))
-
+
def add_complex_field_hyperlink(children):
hyperlink_href = current_hyperlink_href()
if hyperlink_href is None:
return children
else:
return [documents.hyperlink(href=hyperlink_href, children=children)]
-
+
return _ReadResult.map_results(
_read_run_style(properties),
_read_xml_elements(element.children).map(add_complex_field_hyperlink),
@@ -107,8 +114,9 @@ def _create_reader(numbering, content_types, relationships, styles, docx_file, f
is_small_caps=is_small_caps,
vertical_alignment=vertical_alignment,
font=font,
+ font_size=font_size,
))
-
+
def _read_run_style(properties):
return _read_style(properties, "w:rStyle", "Run", styles.find_character_style_by_id)
@@ -121,7 +129,7 @@ def _create_reader(numbering, content_types, relationships, styles, docx_file, f
alignment = properties.find_child_or_null("w:jc").attributes.get("w:val")
numbering = _read_numbering_properties(properties.find_child_or_null("w:numPr"))
indent = _read_paragraph_indent(properties.find_child_or_null("w:ind"))
-
+
return _ReadResult.map_results(
_read_paragraph_style(properties),
_read_xml_elements(element.children),
@@ -133,20 +141,20 @@ def _create_reader(numbering, content_types, relationships, styles, docx_file, f
alignment=alignment,
indent=indent,
)).append_extra()
-
+
def _read_paragraph_style(properties):
return _read_style(properties, "w:pStyle", "Paragraph", styles.find_paragraph_style_by_id)
-
+
current_instr_text = []
complex_field_stack = []
-
+
def current_hyperlink_href():
for complex_field in reversed(complex_field_stack):
if isinstance(complex_field, complex_fields.Hyperlink):
return complex_field.href
return None
-
+
def read_fld_char(element):
fld_char_type = element.attributes.get("w:fldCharType")
if fld_char_type == "begin":
@@ -164,24 +172,24 @@ def _create_reader(numbering, content_types, relationships, styles, docx_file, f
complex_field_stack.pop()
complex_field_stack.append(complex_field)
return _empty_result
-
+
def parse_hyperlink_field_code(instr_text):
result = re.match(r'\s*HYPERLINK "(.*)"', instr_text)
if result is None:
return None
else:
return result.group(1)
-
+
def read_instr_text(element):
current_instr_text.append(_inner_text(element))
return _empty_result
-
+
def _read_style(properties, style_tag_name, style_type, find_style_by_id):
messages = []
style_id = properties \
.find_child_or_null(style_tag_name) \
.attributes.get("w:val")
-
+
if style_id is None:
style_name = None
else:
@@ -191,9 +199,9 @@ def _create_reader(numbering, content_types, relationships, styles, docx_file, f
messages.append(_undefined_style_warning(style_type, style_id))
else:
style_name = style.name
-
+
return _ReadResult([style_id, style_name], [], messages)
-
+
def _undefined_style_warning(style_type, style_id):
return results.warning("{0} style with ID {1} was referenced but not defined in the document".format(style_type, style_id))
@@ -216,31 +224,31 @@ def _create_reader(numbering, content_types, relationships, styles, docx_file, f
def tab(element):
return _success(documents.tab())
-
-
+
+
def no_break_hyphen(element):
return _success(documents.text(unichr(0x2011)))
-
-
+
+
def table(element):
properties = element.find_child_or_null("w:tblPr")
return _ReadResult.map_results(
read_table_style(properties),
_read_xml_elements(element.children)
.flat_map(calculate_row_spans),
-
+
lambda style, children: documents.table(
children=children,
style_id=style[0],
style_name=style[1],
),
)
-
-
+
+
def read_table_style(properties):
return _read_style(properties, "w:tblStyle", "Table", styles.find_table_style_by_id)
-
-
+
+
def table_row(element):
properties = element.find_child_or_null("w:trPr")
is_header = bool(properties.find_child("w:tblHeader"))
@@ -249,19 +257,19 @@ def _create_reader(numbering, content_types, relationships, styles, docx_file, f
children=children,
is_header=is_header,
))
-
-
+
+
def table_cell(element):
properties = element.find_child_or_null("w:tcPr")
gridspan = properties \
.find_child_or_null("w:gridSpan") \
.attributes.get("w:val")
-
+
if gridspan is None:
colspan = 1
else:
colspan = int(gridspan)
-
+
return _read_xml_elements(element.children) \
.map(lambda children: _add_attrs(
documents.table_cell(
@@ -270,7 +278,7 @@ def _create_reader(numbering, content_types, relationships, styles, docx_file, f
),
_vmerge=read_vmerge(properties),
))
-
+
def read_vmerge(properties):
vmerge_element = properties.find_child("w:vMerge")
if vmerge_element is None:
@@ -278,8 +286,8 @@ def _create_reader(numbering, content_types, relationships, styles, docx_file, f
else:
val = vmerge_element.attributes.get("w:val")
return val == "continue" or not val
-
-
+
+
def calculate_row_spans(rows):
unexpected_non_rows = any(
not isinstance(row, documents.TableRow)
@@ -289,7 +297,7 @@ def _create_reader(numbering, content_types, relationships, styles, docx_file, f
return _elements_result_with_messages(rows, [results.warning(
"unexpected non-row element in table, cell merging may be incorrect"
)])
-
+
unexpected_non_cells = any(
not isinstance(cell, documents.TableCell)
for row in rows
@@ -299,7 +307,7 @@ def _create_reader(numbering, content_types, relationships, styles, docx_file, f
return _elements_result_with_messages(rows, [results.warning(
"unexpected non-cell element in table row, cell merging may be incorrect"
)])
-
+
columns = {}
for row in rows:
cell_index = 0
@@ -310,41 +318,41 @@ def _create_reader(numbering, content_types, relationships, styles, docx_file, f
columns[cell_index] = cell
cell._vmerge = False
cell_index += cell.colspan
-
+
for row in rows:
row.children = lists.filter(lambda cell: not cell._vmerge, row.children)
for cell in row.children:
del cell._vmerge
-
+
return _success(rows)
-
-
+
+
def read_child_elements(element):
return _read_xml_elements(element.children)
-
-
+
+
def pict(element):
return read_child_elements(element).to_extra()
-
-
+
+
def hyperlink(element):
relationship_id = element.attributes.get("r:id")
anchor = element.attributes.get("w:anchor")
target_frame = element.attributes.get("w:tgtFrame") or None
children_result = _read_xml_elements(element.children)
-
+
def create(**kwargs):
return children_result.map(lambda children: documents.hyperlink(
children=children,
target_frame=target_frame,
**kwargs
))
-
+
if relationship_id is not None:
href = relationships.find_target_by_relationship_id(relationship_id)
if anchor is not None:
href = replace_fragment(href, anchor)
-
+
return create(href=href)
elif anchor is not None:
return create(anchor=anchor)
@@ -358,8 +366,8 @@ def _create_reader(numbering, content_types, relationships, styles, docx_file, f
return _empty_result
else:
return _success(documents.bookmark(name))
-
-
+
+
def break_(element):
break_type = element.attributes.get("w:type")
@@ -373,7 +381,7 @@ def _create_reader(numbering, content_types, relationships, styles, docx_file, f
warning = results.warning("Unsupported break type: {0}".format(break_type))
return _empty_result_with_message(warning)
-
+
def inline(element):
properties = element.find_child_or_null("wp:docPr").attributes
if properties.get("descr", "").strip():
@@ -386,25 +394,25 @@ def _create_reader(numbering, content_types, relationships, styles, docx_file, f
.find_children("pic:blipFill") \
.find_children("a:blip")
return _read_blips(blips, alt_text)
-
+
def _read_blips(blips, alt_text):
return _ReadResult.concat(lists.map(lambda blip: _read_blip(blip, alt_text), blips))
-
+
def _read_blip(element, alt_text):
return _read_image(lambda: _find_blip_image(element), alt_text)
-
+
def _read_image(find_image, alt_text):
image_path, open_image = find_image()
content_type = content_types.find_content_type(image_path)
image = documents.image(alt_text=alt_text, content_type=content_type, open=open_image)
-
+
if content_type in ["image/png", "image/gif", "image/jpeg", "image/svg+xml", "image/tiff"]:
messages = []
else:
messages = [results.warning("Image of type {0} is unlikely to display in web browsers".format(content_type))]
-
+
return _element_result_with_messages(image, messages)
-
+
def _find_blip_image(element):
embed_relationship_id = element.attributes.get("r:embed")
link_relationship_id = element.attributes.get("r:link")
@@ -412,29 +420,29 @@ def _create_reader(numbering, content_types, relationships, styles, docx_file, f
return _find_embedded_image(embed_relationship_id)
elif link_relationship_id is not None:
return _find_linked_image(link_relationship_id)
-
+
def _find_embedded_image(relationship_id):
target = relationships.find_target_by_relationship_id(relationship_id)
image_path = uri_to_zip_entry_name("word", target)
-
+
def open_image():
image_file = docx_file.open(image_path)
if hasattr(image_file, "__exit__"):
return image_file
else:
return contextlib.closing(image_file)
-
+
return image_path, open_image
-
-
+
+
def _find_linked_image(relationship_id):
image_path = relationships.find_target_by_relationship_id(relationship_id)
-
+
def open_image():
return files.open(image_path)
-
+
return image_path, open_image
-
+
def read_imagedata(element):
relationship_id = element.attributes.get("r:id")
if relationship_id is None:
@@ -443,19 +451,19 @@ def _create_reader(numbering, content_types, relationships, styles, docx_file, f
else:
title = element.attributes.get("o:title")
return _read_image(lambda: _find_embedded_image(relationship_id), title)
-
+
def note_reference_reader(note_type):
def note_reference(element):
return _success(documents.note_reference(note_type, element.attributes["w:id"]))
-
+
return note_reference
-
+
def read_comment_reference(element):
return _success(documents.comment_reference(element.attributes["w:id"]))
def alternate_content(element):
return read_child_elements(element.find_child("mc:Fallback"))
-
+
def read_sdt(element):
return read_child_elements(element.find_child_or_null("w:sdtContent"))
@@ -493,7 +501,7 @@ def _create_reader(numbering, content_types, relationships, styles, docx_file, f
"mc:AlternateContent": alternate_content,
"w:sdt": read_sdt
}
-
+
def read(element):
handler = handlers.get(element.name)
if handler is None:
@@ -504,12 +512,12 @@ def _create_reader(numbering, content_types, relationships, styles, docx_file, f
return _empty_result
else:
return handler(element)
-
+
def _read_xml_elements(nodes):
elements = filter(lambda node: isinstance(node, XmlElement), nodes)
return _ReadResult.concat(lists.map(read, elements))
-
+
return _read_xml_elements
@@ -528,20 +536,20 @@ class _ReadResult(object):
lists.flat_map(lambda result: result.elements, results),
lists.flat_map(lambda result: result.extra, results),
lists.flat_map(lambda result: result.messages, results))
-
-
+
+
@staticmethod
def map_results(first, second, func):
return _ReadResult(
[func(first.elements, second.elements)],
first.extra + second.extra,
first.messages + second.messages)
-
+
def __init__(self, elements, extra, messages):
self.elements = elements
self.extra = extra
self.messages = messages
-
+
def map(self, func):
elements = func(self.elements)
if not isinstance(elements, list):
@@ -550,18 +558,18 @@ class _ReadResult(object):
elements,
self.extra,
self.messages)
-
+
def flat_map(self, func):
result = func(self.elements)
return _ReadResult(
result.elements,
self.extra + result.extra,
self.messages + result.messages)
-
-
+
+
def to_extra(self):
return _ReadResult([], _concat(self.extra, self.elements), self.messages)
-
+
def append_extra(self):
return _ReadResult(_concat(self.elements, self.extra), [], self.messages)
@@ -592,5 +600,5 @@ def _concat(*values):
def _add_attrs(obj, **kwargs):
for key, value in kwargs.items():
setattr(obj, key, value)
-
+
return obj