Skip to content

Commit bd7d13c

Browse files
committed
hdr: add _BaseHeaderFooter._get_or_add_definition()
1 parent c8c6249 commit bd7d13c

File tree

2 files changed

+105
-108
lines changed

2 files changed

+105
-108
lines changed

docx/section.py

Lines changed: 34 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,11 @@ def _add_definition(self):
247247
"""Return newly-added header/footer part."""
248248
raise NotImplementedError("must be implemented by each subclass")
249249

250+
@property
251+
def _definition(self):
252+
"""|HeaderPart| or |FooterPart| object containing header/footer content."""
253+
raise NotImplementedError("must be implemented by each subclass")
254+
250255
def _drop_definition(self):
251256
"""Remove header/footer part containing the definition of this header/footer."""
252257
raise NotImplementedError("must be implemented by each subclass")
@@ -260,16 +265,35 @@ def _get_or_add_definition(self):
260265
"""Return HeaderPart or FooterPart object for this section.
261266
262267
If this header/footer inherits its content, the part for the prior header/footer
263-
is returned. If the definition cannot be inherited (it belongs to the first
264-
section), a new definition is added and then returned.
268+
is returned; this process continue recursively until a definition is found. If
269+
the definition cannot be inherited (because the header/footer belongs to the
270+
first section), a new definition is added for that first section and then
271+
returned.
265272
"""
266-
raise NotImplementedError
273+
# ---note this method is called recursively to access inherited definitions---
274+
# ---case-1: definition is not inherited---
275+
if self._has_definition:
276+
return self._definition
277+
# ---case-2: definition is inherited and belongs to second-or-later section---
278+
prior_headerfooter = self._prior_headerfooter
279+
if prior_headerfooter:
280+
return prior_headerfooter._get_or_add_definition()
281+
# ---case-3: definition is inherited, but belongs to first section---
282+
return self._add_definition()
267283

268284
@property
269285
def _has_definition(self):
270286
"""True if this header/footer has a related part containing its definition."""
271287
raise NotImplementedError("must be implemented by each subclass")
272288

289+
@property
290+
def _prior_headerfooter(self):
291+
"""|_Header| or |_Footer| proxy on prior sectPr element.
292+
293+
Returns None if this is first section.
294+
"""
295+
raise NotImplementedError("must be implemented by each subclass")
296+
273297

274298
class _Footer(_BaseHeaderFooter):
275299
"""Page footer."""
@@ -301,44 +325,25 @@ def _add_definition(self):
301325
self._sectPr.add_headerReference(WD_HEADER_FOOTER.PRIMARY, rId)
302326
return header_part
303327

328+
@property
329+
def _definition(self):
330+
"""|HeaderPart| object containing content of this header."""
331+
headerReference = self._sectPr.get_headerReference(WD_HEADER_FOOTER.PRIMARY)
332+
return self._document_part.header_part(headerReference.rId)
333+
304334
def _drop_definition(self):
305335
"""Remove header definition associated with this section."""
306336
rId = self._sectPr.remove_headerReference(WD_HEADER_FOOTER.PRIMARY)
307337
self._document_part.drop_header_part(rId)
308338

309-
def _get_or_add_definition(self):
310-
"""Return |HeaderPart| object for this header.
311-
312-
If this header inherits its content, the header part for the prior header is
313-
returned; this process continue recursively until a header is found. If this
314-
header cannot inherit (it belongs to the first section), a new header part is
315-
created for the first section and returned.
316-
"""
317-
# ---note this method is called recursively to access inherited headers---
318-
# ---case-1: header does not inherit---
319-
if self._has_definition:
320-
return self._header_part
321-
prior_header = self._prior_header
322-
# ---case-2: header inherits and belongs to second-or-later section---
323-
if prior_header is not None:
324-
return prior_header._get_or_add_definition()
325-
# ---case-3: header inherits, but is first header---
326-
return self._add_definition()
327-
328339
@property
329340
def _has_definition(self):
330341
"""True if a header is explicitly defined for this section."""
331342
headerReference = self._sectPr.get_headerReference(WD_HEADER_FOOTER.PRIMARY)
332343
return False if headerReference is None else True
333344

334345
@property
335-
def _header_part(self):
336-
"""|HeaderPart| object containing content of this header."""
337-
headerReference = self._sectPr.get_headerReference(WD_HEADER_FOOTER.PRIMARY)
338-
return self._document_part.header_part(headerReference.rId)
339-
340-
@property
341-
def _prior_header(self):
346+
def _prior_headerfooter(self):
342347
"""|_Header| proxy on prior sectPr element or None if this is first section."""
343348
preceding_sectPr = self._sectPr.preceding_sectPr
344349
return (

tests/test_section.py

Lines changed: 71 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,51 @@ def it_provides_access_to_the_hdr_or_ftr_element_to_help(
400400
_get_or_add_definition_.assert_called_once_with(header)
401401
assert hdr_elm is hdr
402402

403+
def it_gets_the_definition_when_it_has_one(
404+
self, _has_definition_prop_, _definition_prop_, header_part_
405+
):
406+
_has_definition_prop_.return_value = True
407+
_definition_prop_.return_value = header_part_
408+
header = _BaseHeaderFooter(None, None)
409+
410+
header_part = header._get_or_add_definition()
411+
412+
assert header_part is header_part_
413+
414+
def but_it_gets_the_prior_definition_when_it_is_linked(
415+
self,
416+
_has_definition_prop_,
417+
_prior_headerfooter_prop_,
418+
prior_headerfooter_,
419+
header_part_,
420+
):
421+
_has_definition_prop_.return_value = False
422+
_prior_headerfooter_prop_.return_value = prior_headerfooter_
423+
prior_headerfooter_._get_or_add_definition.return_value = header_part_
424+
header = _BaseHeaderFooter(None, None)
425+
426+
header_part = header._get_or_add_definition()
427+
428+
prior_headerfooter_._get_or_add_definition.assert_called_once_with()
429+
assert header_part is header_part_
430+
431+
def and_it_adds_a_definition_when_it_is_linked_and_the_first_section(
432+
self,
433+
_has_definition_prop_,
434+
_prior_headerfooter_prop_,
435+
_add_definition_,
436+
header_part_
437+
):
438+
_has_definition_prop_.return_value = False
439+
_prior_headerfooter_prop_.return_value = None
440+
_add_definition_.return_value = header_part_
441+
header = _BaseHeaderFooter(None, None)
442+
443+
header_part = header._get_or_add_definition()
444+
445+
_add_definition_.assert_called_once_with(header)
446+
assert header_part is header_part_
447+
403448
# fixtures -------------------------------------------------------
404449

405450
@pytest.fixture(params=[(False, True), (True, False)])
@@ -425,6 +470,10 @@ def is_linked_set_fixture(self, request):
425470
def _add_definition_(self, request):
426471
return method_mock(request, _BaseHeaderFooter, "_add_definition")
427472

473+
@pytest.fixture
474+
def _definition_prop_(self, request):
475+
return property_mock(request, _BaseHeaderFooter, "_definition")
476+
428477
@pytest.fixture
429478
def _drop_definition_(self, request):
430479
return method_mock(request, _BaseHeaderFooter, "_drop_definition")
@@ -441,6 +490,14 @@ def _has_definition_prop_(self, request):
441490
def header_part_(self, request):
442491
return instance_mock(request, HeaderPart)
443492

493+
@pytest.fixture
494+
def prior_headerfooter_(self, request):
495+
return instance_mock(request, _BaseHeaderFooter)
496+
497+
@pytest.fixture
498+
def _prior_headerfooter_prop_(self, request):
499+
return property_mock(request, _BaseHeaderFooter, "_prior_headerfooter")
500+
444501

445502
class Describe_Footer(object):
446503

@@ -512,6 +569,18 @@ def it_can_add_a_header_part_to_help(self, document_part_, header_part_):
512569
)
513570
assert header_part is header_part_
514571

572+
def it_provides_access_to_its_header_part_to_help(
573+
self, document_part_, header_part_
574+
):
575+
sectPr = element("w:sectPr/w:headerReference{w:type=default,r:id=rId8}")
576+
document_part_.header_part.return_value = header_part_
577+
header = _Header(sectPr, document_part_)
578+
579+
header_part = header._definition
580+
581+
document_part_.header_part.assert_called_once_with("rId8")
582+
assert header_part is header_part_
583+
515584
def it_can_drop_the_related_header_part_to_help(self, document_part_):
516585
sectPr = element("w:sectPr{r:a=b}/w:headerReference{w:type=default,r:id=rId42}")
517586
header = _Header(sectPr, document_part_)
@@ -521,47 +590,6 @@ def it_can_drop_the_related_header_part_to_help(self, document_part_):
521590
assert sectPr.xml == xml("w:sectPr{r:a=b}")
522591
document_part_.drop_header_part.assert_called_once_with("rId42")
523592

524-
def it_gets_the_header_part_when_it_has_one(
525-
self, _has_definition_prop_, _header_part_prop_, header_part_
526-
):
527-
_has_definition_prop_.return_value = True
528-
_header_part_prop_.return_value = header_part_
529-
header = _Header(None, None)
530-
531-
header_part = header._get_or_add_definition()
532-
533-
assert header_part is header_part_
534-
535-
def but_it_gets_the_prior_header_part_when_it_is_linked(
536-
self, _has_definition_prop_, _prior_header_prop_, prior_header_, header_part_
537-
):
538-
_has_definition_prop_.return_value = False
539-
_prior_header_prop_.return_value = prior_header_
540-
prior_header_._get_or_add_definition.return_value = header_part_
541-
header = _Header(None, None)
542-
543-
header_part = header._get_or_add_definition()
544-
545-
prior_header_._get_or_add_definition.assert_called_once_with()
546-
assert header_part is header_part_
547-
548-
def and_it_adds_the_header_part_when_it_is_linked_and_the_first_section(
549-
self,
550-
_has_definition_prop_,
551-
_prior_header_prop_,
552-
_add_definition_,
553-
header_part_
554-
):
555-
_has_definition_prop_.return_value = False
556-
_prior_header_prop_.return_value = None
557-
_add_definition_.return_value = header_part_
558-
header = _Header(None, None)
559-
560-
header_part = header._get_or_add_definition()
561-
562-
_add_definition_.assert_called_once_with(header)
563-
assert header_part is header_part_
564-
565593
def it_knows_when_it_has_a_header_part_to_help(self, has_definition_fixture):
566594
sectPr, expected_value = has_definition_fixture
567595
header = _Header(sectPr, None)
@@ -570,18 +598,6 @@ def it_knows_when_it_has_a_header_part_to_help(self, has_definition_fixture):
570598

571599
assert has_definition is expected_value
572600

573-
def it_provides_access_to_its_header_part_to_help(
574-
self, document_part_, header_part_
575-
):
576-
sectPr = element("w:sectPr/w:headerReference{w:type=default,r:id=rId8}")
577-
document_part_.header_part.return_value = header_part_
578-
header = _Header(sectPr, document_part_)
579-
580-
header_part = header._header_part
581-
582-
document_part_.header_part.assert_called_once_with("rId8")
583-
assert header_part is header_part_
584-
585601
def it_provides_access_to_the_prior_Header_to_help(
586602
self, request, document_part_, header_
587603
):
@@ -591,7 +607,7 @@ def it_provides_access_to_the_prior_Header_to_help(
591607
# ---mock must occur after construction of "real" header---
592608
_Header_ = class_mock(request, "docx.section._Header", return_value=header_)
593609

594-
prior_header = header._prior_header
610+
prior_header = header._prior_headerfooter
595611

596612
_Header_.assert_called_once_with(prior_sectPr, document_part_)
597613
assert prior_header is header_
@@ -601,7 +617,7 @@ def but_it_returns_None_when_its_the_first_header(self):
601617
sectPr = doc_elm[0]
602618
header = _Header(sectPr, None)
603619

604-
prior_header = header._prior_header
620+
prior_header = header._prior_headerfooter
605621

606622
assert prior_header is None
607623

@@ -619,38 +635,14 @@ def has_definition_fixture(self, request):
619635

620636
# fixture components ---------------------------------------------
621637

622-
@pytest.fixture
623-
def _add_definition_(self, request):
624-
return method_mock(request, _Header, "_add_definition")
625-
626638
@pytest.fixture
627639
def document_part_(self, request):
628640
return instance_mock(request, DocumentPart)
629641

630-
@pytest.fixture
631-
def _get_or_add_definition_(self, request):
632-
return method_mock(request, _Header, "_get_or_add_definition")
633-
634-
@pytest.fixture
635-
def _has_definition_prop_(self, request):
636-
return property_mock(request, _Header, "_has_definition")
637-
638642
@pytest.fixture
639643
def header_(self, request):
640644
return instance_mock(request, _Header)
641645

642646
@pytest.fixture
643647
def header_part_(self, request):
644648
return instance_mock(request, HeaderPart)
645-
646-
@pytest.fixture
647-
def _header_part_prop_(self, request):
648-
return property_mock(request, _Header, "_header_part")
649-
650-
@pytest.fixture
651-
def prior_header_(self, request):
652-
return instance_mock(request, _Header)
653-
654-
@pytest.fixture
655-
def _prior_header_prop_(self, request):
656-
return property_mock(request, _Header, "_prior_header")

0 commit comments

Comments
 (0)