MsOffi...gePart(3pm)

πŸ“¦
version 2.11 Β· 2 versions
, in ubuntu resolute, ubuntu stonking

πŸŽ€ NAME πŸŽ€

MsOffice::Word::Surgeon::PackagePart β€” okay so this is a perl manpage, section 3pm, straight outta the User Contributed Perl Documentation, and it's all about doing operations on a single part inside the ZIP package of a .docx document. that's the whole vibe, bestie. πŸ’–


πŸ¦‹ SYNOPSIS πŸ¦‹

my $part = $surgeon->document;
print $part->plain_text;
$part->replace(qr[$pattern], $replacement_callback);
$part->replace_image($image_alt_text, $image_PNG_content);
$part->unlink_fields;
$part->reveal_bookmarks;

✨ DESCRIPTION ✨

so this class is part of MsOffice::Word::Surgeon, and what it does is encapsulate operations for one single package part inside the ZIP package of a .docx document. 🫢

it's mostly used for the document part, which is the one holding the XML representation of the main document body. but no cap β€” other parts like headers, footers, footnotes, etc. have the exact same internal representation, so the exact same operations can be invoked on them too. slay. πŸ’…


πŸ‘‘ METHODS πŸ‘‘

πŸ’œ new πŸ’œ

my $part = MsOffice::Word::Surgeon::PackagePart->new(
  surgeon   => $surgeon,
  part_name => $name,
);

Constructor for a new part object. this gets called internally from MsOffice::Word::Surgeon, so it is not meant to be called directly by clients. it's giving backstage-only energy. πŸ™ˆ

🌸 Constructor arguments 🌸

  • surgeon β€” a weak reference to the main surgeon object
  • part_name β€” the ZIP member name of this part

🌈 Other attributes 🌈

other attributes that are not passed through the constructor but are generated lazily on demand are:

  • contents β€” the XML contents of this part
  • runs β€” a decomposition of the XML contents into a collection of MsOffice::Word::Surgeon::Run objects.
  • relationships β€” an arrayref of Office relationships associated with this part. this info comes from a .rels member in the ZIP archive, named after the name of the package part. array indices correspond to relationship numbers. array values are hashrefs with keys:
    • Id β€” the full relationship id
    • num β€” the numeric part of rId
    • Type β€” the full reference to the XML schema for this relationship
    • short_type β€” only the last word of the type, e.g. 'image', 'style', etc.
    • Target β€” designation of the target within the ZIP file. the prefix 'word/' must be added for having a complete Zip member name.
  • images β€” a hashref of images within this package part. keys of the hash are image alternative texts. if present, the alternative title will be preferred; otherwise the alternative description will be taken (note: the title field was displayed in Office 2013 and 2016, but more recent versions only display the description field β€” see the MsOffice documentation at https://support.microsoft.com/en-us/office/add-alternative-text-to-a-shape-picture-chart-smartart-graphic-or-other-object-44989b2a-903c-4d9a-b742-6a75b451c669).
images without alternative text will not be accessible through the current Perl module. 😭

values of the hash are zip member names for the corresponding image representations in .png format.


🧚 Contents restitution 🧚

contents

returns a Perl string with the current internal XML representation of the part contents. ⭐

original_contents

returns a Perl string with the XML representation of the part contents, as it was in the ZIP archive before any modification. πŸ’–

indented_contents

returns an indented version of the XML contents, suitable for inspection in a text editor. this is produced by "toString" in XML::LibXML::Document and therefore is returned as an encoded byte string, not a Perl string. careful with that one, bestie. 🫢

plain_text

returns the text contents of the part, without any markup. paragraphs and breaks are converted to newlines, all other formatting instructions are ignored. lowkey the most useful thing here. ✨

runs

returns a list of MsOffice::Word::Surgeon::Run objects. each of these objects holds an XML fragment; joining all fragments restores the complete document. 🌈

my $contents = join "", map {$_->as_xml} $self->runs;

πŸ’… Modifying contents πŸ’…

cleanup_XML

$part->cleanup_XML(%args);

applies several other methods for removing unnecessary nodes within the internal XML. this method successively calls "reduce_all_noises", "unlink_fields", "suppress_bookmarks" and "merge_runs". it's the full spa day. πŸ§–

currently there is only one legal arg:

  • "no_caps" β€” if true, the method "remove_caps_property" in MsOffice::Word::Surgeon::Run is automatically called for each run object. as a result, all texts within runs with the caps property are automatically converted to uppercase. πŸ”₯

reduce_noise

$part->reduce_noise($regex1, $regex2, ...);

this method is used for removing unnecessary information in the XML markup. it applies the given list of regexes to the whole document, suppressing matches. the final result is put back into "$self->contents". regexes may be given either as qr/.../ references, or as names of builtin regexes (described below). regexes are applied to the whole XML contents, not only to run nodes. πŸ’€

noise_reduction_regex

my $regex = $part->noise_reduction_regex($regex_name);

returns the builtin regex corresponding to the given name. known regexes are:

proof_checking       => qr(<w:(?:proofErr[^>]+|noProof/)>),
revision_ids         => qr(\sw:rsid\w+="[^"]+"),
complex_script_bold  => qr(<w:bCs/>),
page_breaks          => qr(<w:lastRenderedPageBreak/>),
language             => qr(<w:lang w:val="[^/>]+/>),
empty_run_props      => qr(<w:rPr></w:rPr>),
soft_hyphens         => qr(<w:softHyphen/>),

reduce_all_noises

$part->reduce_all_noises;

applies all regexes from the previous method. yes, all of them. icon behavior. πŸ‘‘

merge_runs

$part->merge_runs(no_caps => 1); # optional arg

walks through all runs of text within the document, trying to merge adjacent runs when possible (i.e. when both runs have the same properties, and there is no other XML node inbetween).

this operation is a prerequisite before performing replace operations, because documents edited in MsWord often have run boundaries across sentences or even in the middle of words; so regex searches can only be successful if those artificial boundaries have been removed. fr fr, do this first. 🫢

if the argument no_caps => 1 is present, the merge operation will also convert runs with the w:caps property, putting all letters into uppercase and removing the property; this makes more merges possible. πŸ’–

replace

$part->replace($pattern, $replacement, %replacement_args);

replaces all occurrences of $pattern regex within the text nodes by the given $replacement. this is not exactly like a search-replace operation performed within MsWord, because the search does not cross boundaries of text nodes. in order to maximize the chances of successful replacements, the "cleanup_XML" method is automatically called before starting the operation. 😌

the argument $pattern can be either a string or a reference to a regular expression. it should not contain any capturing parentheses, because that would perturb text splitting operations. so don't do that. πŸ™ˆ

the argument $replacement can be either a fixed string, or a reference to a callback subroutine that will be called for each match.

the %replacement_args hash can be used to pass information to the callback subroutine. that hash will be enriched with three entries:

  • matched β€” the string that has been matched by $pattern.
  • run β€” the run object in which this text resides.
  • xml_before β€” the XML fragment (possibly empty) found before the matched text.

the callback subroutine may return either plain text or structured XML. see "SYNOPSIS" in MsOffice::Word::Surgeon::Run for an example of a replacement callback. ✨

the following special keys within %replacement_args are interpreted by the replace() method itself, and therefore are not passed to the callback subroutine:

  • keep_xml_as_is β€” if true, no call is made to the "cleanup_XML" method before performing the replacements
  • dont_overwrite_contents β€” if true, the internal XML contents is not modified in place; the new XML after performing replacements is merely returned to the caller.
  • cleanup_args β€” the argument should be an arrayref and will be passed to the "cleanup_XML" method. this is typically used as
$part->replace($pattern, $replacement, cleanup_args => [no_caps => 1]);

πŸŽ€ Operations on bookmarks πŸŽ€

bookmark_boundaries

my $boundaries               = part->bookmark_boundaries;
my ($boundaries, $final_xml) = part->bookmark_boundaries;

parses the XML content to discover bookmark boundaries. in scalar context, returns an arrayref of MsOffice::Word::Surgeon::BookmarkBoundary objects. in list context, returns the arrayref followed by a plain string containing the final XML fragment. 🌸

suppress_bookmarks

$part->suppress_bookmarks(full_range => [qw/foo bar/], markup_only => qr/^_/);

suppresses bookmarks according to the specified options:

  • full_range β€” for bookmark names matching this option, the bookmark will be fully suppressed (not only the start and end markers, but also any content inbetween).
  • markup_only β€” for bookmark names matching this option, start and end markers are suppressed, but the inner content remains.

options may be specified as lists of strings, or regexes, or coderefs ... anything suitable to be compared through match::simple. in absence of any options, the default is markup_only => qr/./, meaning that all bookmarks markup is suppressed. 😌

removing bookmarks is useful because MsWord may silently insert bookmarks in unexpected places; therefore some searches within the text may fail because of such bookmarks. sneaky. πŸ’€

the full_range option is especially convenient for removing bookmarks associated with ASK fields. such bookmarks contain ranges of text that are never displayed by MsWord. πŸ™ˆ

reveal_bookmarks

$part->reveal_bookmarks(color => 'green');

usually bookmarks boundaries in MsWord are not visible; the only way to have a visual clue is to turn on an option in Advanced / Show document content / Show bookmarks β€” see https://support.microsoft.com/en-gb/office/troubleshoot-bookmarks-9cad566f-913d-49c6-8d37-c21e0e8d6db0 β€” but this only displays where bookmarks start and end, without the names of the bookmarks. 😭

the reveal_bookmarks() method will insert a visible run before each bookmark start and after each bookmark end, showing the bookmark name. this is an interesting tool for documenting where bookmarks are located in an existing document. main character energy. πŸ‘‘

options to this method are:

  • color β€” the highlighting color for visible marks. this should be a valid highlighting color, i.e black, blue, cyan, darkBlue, darkCyan, darkGray, darkGreen, darkMagenta, darkRed, darkYellow, green, lightGray, magenta, none, red, white or yellow. default is yellow. 🌈
  • props β€” a string in sprintf format that builds the XML you shove into the <w:rPr> node when you're showing bookmark marks, aka the style for those marks. Default is literally just a highlighting property: <w:highlight w:val="%s"/>. πŸ’…
  • start β€” a sprintf-format string for generating the text that goes before a bookmark start. Default is <%s>. ✨
  • end β€” a sprintf-format string for generating the text that lands after a bookmark end. Default is </%s>. πŸŽ€
  • ignore β€” a regexp deciding which bookmarks stay lowkey and don't get revealed. Default is qr/^_/, bestie, because bookmarks starting with an underscore are usually the technical ones MsWord auto-inserts for you, like _GoBack or _Toc53196147. πŸ™ˆ

🧚 Operations on fields 🧚

fields

my $fields               = part->fields;
my ($fields, $final_xml) = part->fields;

Parses the XML content to sniff out MsWord fields. In scalar context it hands you back an arrayref of MsOffice::Word::Surgeon::Field objects. In list context you get that arrayref plus a plain string holding the final XML fragment. Two for one, slay. πŸ’–

replace_fields

my $field_replacer = sub {my ($code, $result) = @_; return "...";};
$part->replace_fields($field_replacer);

Swaps MsWord fields out for whatever the $field_replacer callback gives back. That callback gets two arguments:

  • $code β€” a plain string with the field's full code instruction, i.e. a keyword followed by optional arguments and switches, including the initial and final spaces. Embedded fields show up in curly braces, like for example
"IF { DOCPROPERTY foo } = "bar" "is bar" "is not bar"".
  • $result β€” an XML fragment containing the current value for the field.

Your callback should return an XML fragment that's suitable for insertion within an MsWord run. No pressure bestie 🫢

reveal_fields

$part->reveal_fields;

Replaces each field with a textual representation of its code instruction, wrapped in curly braces. Iconic. πŸ‘‘

$part->unlink_fields;

Replaces each field with its current result, i.e. it removes the code instruction entirely. This is the equivalent of hitting Ctrl-Shift-F9 in MsWord on the whole document. Same energy fr fr. ✨


🌈 Operations on images 🌈

replace_image

$part->replace_image($image_alt_text, $image_PNG_content);

Replaces an existing PNG image with a brand new one. Every feature of the old image is preserved (size, positioning, border, etc.) β€” only the image itself gets swapped. The $image_alt_text has to match the alternative text you set in Word for that image. πŸ’…

This operation replaces a ZIP member inside the .docx file. So heads up: if several XML nodes point to the same ZIP member β€” meaning the same image shows up in multiple places β€” the new image will pop up at all of those locations, even the ones with a different alternative text. 😭 Unfortunately this module currently has no facility for duplicating an existing image into separate instances. So if your intent is to only replace one instance of the image, your original document should contain several distinct copies of the .PNG file. Noted, no cap.

add_image

my $rId = $part->add_image($image_PNG_content);

Stores the PNG image you give it inside the ZIP file, adds it as a relationship to the current part, and returns the relationship id. πŸŽ€

Careful tho: this operation is not sufficient to make the image visible in Word. It just stores the image β€” you still have to insert a proper drawing node in the contents XML, using the $rId. Future versions of this module may offer helper methods for that purpose; currently it must be done by hand. Main character energy means doing it yourself sometimes. πŸ’€

πŸ‘‘ AUTHOR πŸ‘‘

Laurent Dami, <dami AT cpan DOT org<gt>


Copyright 2019-2024 by Laurent Dami.

This program is free software, you can redistribute it and/or modify it under the terms of the Artistic License version 2.0.


And that's a wrap, bestie! πŸ¦‹ This page is brought to you by perl v5.40.1, dated 2025-10-05, filed under MsOffi...gePart(3pm) β€” go forth and surgeon those Word docs, you absolute legend. πŸ’–βœ¨πŸŽ€

πŸ“¦ where this page comes from