def METHOD_NAME(
    d: DocInfo,
    args: ReplaceArguments,
) -> Generator[ReplacementResult | AttrModification, None, None]:
    """Performs a search-and-replace operation for the specified document, yielding list of :class:`ReplacementResult` or :class:`AttrModification`.
    :param args: The replacement arguments. If args.dryrun is True, no actual replacement will occur.
    :param d: The document to process.
    """
    for r in search(d, args, use_exported=False):
        repl: None | ReplacementResult | AttrModification = None
        old_md = None
        new_md = None
        mi = r.par.doc.get_settings().get_macroinfo(default_view_ctx)
        if args.to is not None:
            repl = ReplacementResult(search_result=r, replacement=args.to)
            old_md = r.par.get_markdown()
            new_md = repl.get_new_markdown()
            if r.par.is_yaml():
                try:
                    yb = YamlBlock.from_markdown(r.par.get_expanded_markdown(mi))
                except YAMLError:
                    repl.error = (
                        f"YAML is invalid before replacement, so not doing anything"
                    )
                except TimDbException as e:
                    repl.error = f"Exception: {str(e)}"
                if not repl.error:
                    try:
                        p_temp = r.par.clone()
                        p_temp.set_markdown(new_md)
                        yb_new = YamlBlock.from_markdown(
                            p_temp.get_expanded_markdown(mi)
                        )
                    except YAMLError:
                        repl.error = "YAML would be invalid after replacement, so not doing anything"
            yield repl
        old_attrs = copy(r.par.get_attrs())
        if args.add_attr is not None:
            attr_name, attr_value = args.add_attr.split("=")
            repl = AttrModification(
                search_result=r,
                par=r.par,
                name=attr_name,
                value=attr_value,
                was_already=old_attrs.get(attr_name) == attr_value,
            )
            yield repl
            r.par.set_attr(attr_name, attr_value)
        if not repl:
            raise Exception("--to or --add-attr must be given")
        if not args.dryrun and not repl.error:
            # The method get_new_markdown replaces all occurrences in a paragraph,
            # so some ReplacementResults are (in this sense) redundant.
            if old_md == new_md and old_attrs == r.par.get_attrs():
                continue
            if new_md is not None:
                r.par.set_markdown(new_md)
            # TODO currently DocParagraph.store doesn't take self.attrs into account; it only saves __data dict.
            #  So for now we just modify the dict reference directly.
            r.par.clear_cache()
            r.par.save()