<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">import pytest

from bs4.element import Tag
from bs4.formatter import (
    Formatter,
    HTMLFormatter,
    XMLFormatter,
)
from . import SoupTest

class TestFormatter(SoupTest):

    def test_default_attributes(self):
        # Test the default behavior of Formatter.attributes().
        formatter = Formatter()
        tag = Tag(name="tag")
        tag['b'] = 1
        tag['a'] = 2

        # Attributes come out sorted by name. In Python 3, attributes
        # normally come out of a dictionary in the order they were
        # added.
        assert [('a', 2), ('b', 1)] == formatter.attributes(tag)

        # This works even if Tag.attrs is None, though this shouldn't
        # normally happen.
        tag.attrs = None
        assert [] == formatter.attributes(tag)

        assert ' ' == formatter.indent
        
    def test_sort_attributes(self):
        # Test the ability to override Formatter.attributes() to,
        # e.g., disable the normal sorting of attributes.
        class UnsortedFormatter(Formatter):
            def attributes(self, tag):
                self.called_with = tag
                for k, v in sorted(tag.attrs.items()):
                    if k == 'ignore':
                        continue
                    yield k,v

        soup = self.soup('&lt;p cval="1" aval="2" ignore="ignored"&gt;&lt;/p&gt;')
        formatter = UnsortedFormatter()
        decoded = soup.decode(formatter=formatter)

        # attributes() was called on the &lt;p&gt; tag. It filtered out one
        # attribute and sorted the other two.
        assert formatter.called_with == soup.p
        assert '&lt;p aval="2" cval="1"&gt;&lt;/p&gt;' == decoded

    def test_empty_attributes_are_booleans(self):
        # Test the behavior of empty_attributes_are_booleans as well
        # as which Formatters have it enabled.
        
        for name in ('html', 'minimal', None):
            formatter = HTMLFormatter.REGISTRY[name]
            assert False == formatter.empty_attributes_are_booleans

        formatter = XMLFormatter.REGISTRY[None]
        assert False == formatter.empty_attributes_are_booleans

        formatter = HTMLFormatter.REGISTRY['html5']
        assert True == formatter.empty_attributes_are_booleans

        # Verify that the constructor sets the value.
        formatter = Formatter(empty_attributes_are_booleans=True)
        assert True == formatter.empty_attributes_are_booleans

        # Now demonstrate what it does to markup.
        for markup in (
                "&lt;option selected&gt;&lt;/option&gt;",
                '&lt;option selected=""&gt;&lt;/option&gt;'
        ):
            soup = self.soup(markup)
            for formatter in ('html', 'minimal', 'xml', None):
                assert b'&lt;option selected=""&gt;&lt;/option&gt;' == soup.option.encode(formatter='html')
                assert b'&lt;option selected&gt;&lt;/option&gt;' == soup.option.encode(formatter='html5')

    @pytest.mark.parametrize(
        "indent,expect",
        [
            (None, '&lt;a&gt;\n&lt;b&gt;\ntext\n&lt;/b&gt;\n&lt;/a&gt;\n'),
            (-1, '&lt;a&gt;\n&lt;b&gt;\ntext\n&lt;/b&gt;\n&lt;/a&gt;\n'),
            (0, '&lt;a&gt;\n&lt;b&gt;\ntext\n&lt;/b&gt;\n&lt;/a&gt;\n'),
            ("", '&lt;a&gt;\n&lt;b&gt;\ntext\n&lt;/b&gt;\n&lt;/a&gt;\n'),

            (1, '&lt;a&gt;\n &lt;b&gt;\n  text\n &lt;/b&gt;\n&lt;/a&gt;\n'),
            (2, '&lt;a&gt;\n  &lt;b&gt;\n    text\n  &lt;/b&gt;\n&lt;/a&gt;\n'),

            ("\t", '&lt;a&gt;\n\t&lt;b&gt;\n\t\ttext\n\t&lt;/b&gt;\n&lt;/a&gt;\n'),
            ('abc', '&lt;a&gt;\nabc&lt;b&gt;\nabcabctext\nabc&lt;/b&gt;\n&lt;/a&gt;\n'),

            # Some invalid inputs -- the default behavior is used.
            (object(), '&lt;a&gt;\n &lt;b&gt;\n  text\n &lt;/b&gt;\n&lt;/a&gt;\n'),
            (b'bytes', '&lt;a&gt;\n &lt;b&gt;\n  text\n &lt;/b&gt;\n&lt;/a&gt;\n'),
        ]
    )
    def test_indent(self, indent, expect):
        # Pretty-print a tree with a Formatter set to
        # indent in a certain way and verify the results.
        soup = self.soup("&lt;a&gt;&lt;b&gt;text&lt;/b&gt;&lt;/a&gt;")
        formatter = Formatter(indent=indent)
        assert soup.prettify(formatter=formatter) == expect

        # Pretty-printing only happens with prettify(), not
        # encode().
        assert soup.encode(formatter=formatter) != expect
        
    def test_default_indent_value(self):
        formatter = Formatter()
        assert formatter.indent == ' '

</pre></body></html>