Best Python code snippet using playwright-python
test_contentmanager.py
Source:test_contentmanager.py  
...21        cm.add_get_handler(key, foo_getter)22        m = self._make_message()23        m['Content-Type'] = 'text/plain'24        m['X-Bar-Header'] = 'foo'25        self.assertEqual(cm.get_content(m, foo='bar'), ('bar', 'foo'))26    def get_key_as_get_content_key_order(self, order, key):27        def bar_getter(msg):28            return msg['X-Bar-Header']29        def foo_getter(msg):30            return msg['X-Foo-Header']31        cm = ContentManager()32        cm.add_get_handler(key, foo_getter)33        for precedence, key in self.get_key_params.values():34            if precedence > order:35                cm.add_get_handler(key, bar_getter)36        m = self._make_message()37        m['Content-Type'] = 'text/plain'38        m['X-Bar-Header'] = 'bar'39        m['X-Foo-Header'] = 'foo'40        self.assertEqual(cm.get_content(m), ('foo'))41    def test_get_content_raises_if_unknown_mimetype_and_no_default(self):42        cm = ContentManager()43        m = self._make_message()44        m['Content-Type'] = 'text/plain'45        with self.assertRaisesRegex(KeyError, 'text/plain'):46            cm.get_content(m)47    class BaseThing(str):48        pass49    baseobject_full_path = __name__ + '.' + 'TestContentManager.BaseThing'50    class Thing(BaseThing):51        pass52    testobject_full_path = __name__ + '.' + 'TestContentManager.Thing'53    set_key_params = {54        'type':             (0,  Thing,),55        'full_path':        (1,  testobject_full_path,),56        'qualname':         (2,  'TestContentManager.Thing',),57        'name':             (3,  'Thing',),58        'base_type':        (4,  BaseThing,),59        'base_full_path':   (5,  baseobject_full_path,),60        'base_qualname':    (6,  'TestContentManager.BaseThing',),61        'base_name':        (7,  'BaseThing',),62        'str_type':         (8,  str,),63        'str_full_path':    (9,  'builtins.str',),64        'str_name':         (10, 'str',),   # str name and qualname are the same65        'null_key':         (11, None,),66        }67    def set_key_as_set_content_key(self, order, key):68        def foo_setter(msg, obj, foo=None):69            msg['X-Foo-Header'] = foo70            msg.set_payload(obj)71        cm = ContentManager()72        cm.add_set_handler(key, foo_setter)73        m = self._make_message()74        msg_obj = self.Thing()75        cm.set_content(m, msg_obj, foo='bar')76        self.assertEqual(m['X-Foo-Header'], 'bar')77        self.assertEqual(m.get_payload(), msg_obj)78    def set_key_as_set_content_key_order(self, order, key):79        def foo_setter(msg, obj):80            msg['X-FooBar-Header'] = 'foo'81            msg.set_payload(obj)82        def bar_setter(msg, obj):83            msg['X-FooBar-Header'] = 'bar'84        cm = ContentManager()85        cm.add_set_handler(key, foo_setter)86        for precedence, key in self.get_key_params.values():87            if precedence > order:88                cm.add_set_handler(key, bar_setter)89        m = self._make_message()90        msg_obj = self.Thing()91        cm.set_content(m, msg_obj)92        self.assertEqual(m['X-FooBar-Header'], 'foo')93        self.assertEqual(m.get_payload(), msg_obj)94    def test_set_content_raises_if_unknown_type_and_no_default(self):95        cm = ContentManager()96        m = self._make_message()97        msg_obj = self.Thing()98        with self.assertRaisesRegex(KeyError, self.testobject_full_path):99            cm.set_content(m, msg_obj)100    def test_set_content_raises_if_called_on_multipart(self):101        cm = ContentManager()102        m = self._make_message()103        m['Content-Type'] = 'multipart/foo'104        with self.assertRaises(TypeError):105            cm.set_content(m, 'test')106    def test_set_content_calls_clear_content(self):107        m = self._make_message()108        m['Content-Foo'] = 'bar'109        m['Content-Type'] = 'text/html'110        m['To'] = 'test'111        m.set_payload('abc')112        cm = ContentManager()113        cm.add_set_handler(str, lambda *args, **kw: None)114        m.set_content('xyz', content_manager=cm)115        self.assertIsNone(m['Content-Foo'])116        self.assertIsNone(m['Content-Type'])117        self.assertEqual(m['To'], 'test')118        self.assertIsNone(m.get_payload())119@parameterize120class TestRawDataManager(TestEmailBase):121    # Note: these tests are dependent on the order in which headers are added122    # to the message objects by the code.  There's no defined ordering in123    # RFC5322/MIME, so this makes the tests more fragile than the standards124    # require.  However, if the header order changes it is best to understand125    # *why*, and make sure it isn't a subtle bug in whatever change was126    # applied.127    policy = policy.default.clone(max_line_length=60,128                                  content_manager=raw_data_manager)129    message = EmailMessage130    def test_get_text_plain(self):131        m = self._str_msg(textwrap.dedent("""\132            Content-Type: text/plain133            Basic text.134            """))135        self.assertEqual(raw_data_manager.get_content(m), "Basic text.\n")136    def test_get_text_html(self):137        m = self._str_msg(textwrap.dedent("""\138            Content-Type: text/html139            <p>Basic text.</p>140            """))141        self.assertEqual(raw_data_manager.get_content(m),142                         "<p>Basic text.</p>\n")143    def test_get_text_plain_latin1(self):144        m = self._bytes_msg(textwrap.dedent("""\145            Content-Type: text/plain; charset=latin1146            Basìc tëxt.147            """).encode('latin1'))148        self.assertEqual(raw_data_manager.get_content(m), "Basìc tëxt.\n")149    def test_get_text_plain_latin1_quoted_printable(self):150        m = self._str_msg(textwrap.dedent("""\151            Content-Type: text/plain; charset="latin-1"152            Content-Transfer-Encoding: quoted-printable153            Bas=ECc t=EBxt.154            """))155        self.assertEqual(raw_data_manager.get_content(m), "Basìc tëxt.\n")156    def test_get_text_plain_utf8_base64(self):157        m = self._str_msg(textwrap.dedent("""\158            Content-Type: text/plain; charset="utf8"159            Content-Transfer-Encoding: base64160            QmFzw6xjIHTDq3h0Lgo=161            """))162        self.assertEqual(raw_data_manager.get_content(m), "Basìc tëxt.\n")163    def test_get_text_plain_bad_utf8_quoted_printable(self):164        m = self._str_msg(textwrap.dedent("""\165            Content-Type: text/plain; charset="utf8"166            Content-Transfer-Encoding: quoted-printable167            Bas=c3=acc t=c3=abxt=fd.168            """))169        self.assertEqual(raw_data_manager.get_content(m), "Basìc tëxt�.\n")170    def test_get_text_plain_bad_utf8_quoted_printable_ignore_errors(self):171        m = self._str_msg(textwrap.dedent("""\172            Content-Type: text/plain; charset="utf8"173            Content-Transfer-Encoding: quoted-printable174            Bas=c3=acc t=c3=abxt=fd.175            """))176        self.assertEqual(raw_data_manager.get_content(m, errors='ignore'),177                         "Basìc tëxt.\n")178    def test_get_text_plain_utf8_base64_recoverable_bad_CTE_data(self):179        m = self._str_msg(textwrap.dedent("""\180            Content-Type: text/plain; charset="utf8"181            Content-Transfer-Encoding: base64182            QmFzw6xjIHTDq3h0Lgo\xFF=183            """))184        self.assertEqual(raw_data_manager.get_content(m, errors='ignore'),185                         "Basìc tëxt.\n")186    def test_get_text_invalid_keyword(self):187        m = self._str_msg(textwrap.dedent("""\188            Content-Type: text/plain189            Basic text.190            """))191        with self.assertRaises(TypeError):192            raw_data_manager.get_content(m, foo='ignore')193    def test_get_non_text(self):194        template = textwrap.dedent("""\195            Content-Type: {}196            Content-Transfer-Encoding: base64197            Ym9ndXMgZGF0YQ==198            """)199        for maintype in 'audio image video application'.split():200            with self.subTest(maintype=maintype):201                m = self._str_msg(template.format(maintype+'/foo'))202                self.assertEqual(raw_data_manager.get_content(m), b"bogus data")203    def test_get_non_text_invalid_keyword(self):204        m = self._str_msg(textwrap.dedent("""\205            Content-Type: image/jpg206            Content-Transfer-Encoding: base64207            Ym9ndXMgZGF0YQ==208            """))209        with self.assertRaises(TypeError):210            raw_data_manager.get_content(m, errors='ignore')211    def test_get_raises_on_multipart(self):212        m = self._str_msg(textwrap.dedent("""\213            Content-Type: multipart/mixed; boundary="==="214            --===215            --===--216            """))217        with self.assertRaises(KeyError):218            raw_data_manager.get_content(m)219    def test_get_message_rfc822_and_external_body(self):220        template = textwrap.dedent("""\221            Content-Type: message/{}222            To: foo@example.com223            From: bar@example.com224            Subject: example225            an example message226            """)227        for subtype in 'rfc822 external-body'.split():228            with self.subTest(subtype=subtype):229                m = self._str_msg(template.format(subtype))230                sub_msg = raw_data_manager.get_content(m)231                self.assertIsInstance(sub_msg, self.message)232                self.assertEqual(raw_data_manager.get_content(sub_msg),233                                 "an example message\n")234                self.assertEqual(sub_msg['to'], 'foo@example.com')235                self.assertEqual(sub_msg['from'].addresses[0].username, 'bar')236    def test_get_message_non_rfc822_or_external_body_yields_bytes(self):237        m = self._str_msg(textwrap.dedent("""\238            Content-Type: message/partial239            To: foo@example.com240            From: bar@example.com241            Subject: example242            The real body is in another message.243            """))244        self.assertEqual(raw_data_manager.get_content(m)[:10], b'To: foo@ex')245    def test_set_text_plain(self):246        m = self._make_message()247        content = "Simple message.\n"248        raw_data_manager.set_content(m, content)249        self.assertEqual(str(m), textwrap.dedent("""\250            Content-Type: text/plain; charset="utf-8"251            Content-Transfer-Encoding: 7bit252            Simple message.253            """))254        self.assertEqual(m.get_payload(decode=True).decode('utf-8'), content)255        self.assertEqual(m.get_content(), content)256    def test_set_text_html(self):257        m = self._make_message()258        content = "<p>Simple message.</p>\n"259        raw_data_manager.set_content(m, content, subtype='html')260        self.assertEqual(str(m), textwrap.dedent("""\261            Content-Type: text/html; charset="utf-8"262            Content-Transfer-Encoding: 7bit263            <p>Simple message.</p>264            """))265        self.assertEqual(m.get_payload(decode=True).decode('utf-8'), content)266        self.assertEqual(m.get_content(), content)267    def test_set_text_charset_latin_1(self):268        m = self._make_message()269        content = "Simple message.\n"270        raw_data_manager.set_content(m, content, charset='latin-1')271        self.assertEqual(str(m), textwrap.dedent("""\272            Content-Type: text/plain; charset="iso-8859-1"273            Content-Transfer-Encoding: 7bit274            Simple message.275            """))276        self.assertEqual(m.get_payload(decode=True).decode('utf-8'), content)277        self.assertEqual(m.get_content(), content)278    def test_set_text_short_line_minimal_non_ascii_heuristics(self):279        m = self._make_message()280        content = "et là il est monté sur moi et il commence à m'éto.\n"281        raw_data_manager.set_content(m, content)282        self.assertEqual(bytes(m), textwrap.dedent("""\283            Content-Type: text/plain; charset="utf-8"284            Content-Transfer-Encoding: 8bit285            et là il est monté sur moi et il commence à m'éto.286            """).encode('utf-8'))287        self.assertEqual(m.get_payload(decode=True).decode('utf-8'), content)288        self.assertEqual(m.get_content(), content)289    def test_set_text_long_line_minimal_non_ascii_heuristics(self):290        m = self._make_message()291        content = ("j'ai un problème de python. il est sorti de son"292                   " vivarium.  et là il est monté sur moi et il commence"293                   " à m'éto.\n")294        raw_data_manager.set_content(m, content)295        self.assertEqual(bytes(m), textwrap.dedent("""\296            Content-Type: text/plain; charset="utf-8"297            Content-Transfer-Encoding: quoted-printable298            j'ai un probl=C3=A8me de python. il est sorti de son vivari=299            um.  et l=C3=A0 il est mont=C3=A9 sur moi et il commence =300            =C3=A0 m'=C3=A9to.301            """).encode('utf-8'))302        self.assertEqual(m.get_payload(decode=True).decode('utf-8'), content)303        self.assertEqual(m.get_content(), content)304    def test_set_text_11_lines_long_line_minimal_non_ascii_heuristics(self):305        m = self._make_message()306        content = '\n'*10 + (307                  "j'ai un problème de python. il est sorti de son"308                  " vivarium.  et là il est monté sur moi et il commence"309                  " à m'éto.\n")310        raw_data_manager.set_content(m, content)311        self.assertEqual(bytes(m), textwrap.dedent("""\312            Content-Type: text/plain; charset="utf-8"313            Content-Transfer-Encoding: quoted-printable314            """ + '\n'*10 + """315            j'ai un probl=C3=A8me de python. il est sorti de son vivari=316            um.  et l=C3=A0 il est mont=C3=A9 sur moi et il commence =317            =C3=A0 m'=C3=A9to.318            """).encode('utf-8'))319        self.assertEqual(m.get_payload(decode=True).decode('utf-8'), content)320        self.assertEqual(m.get_content(), content)321    def test_set_text_maximal_non_ascii_heuristics(self):322        m = self._make_message()323        content = "áàäéèÄöÅ.\n"324        raw_data_manager.set_content(m, content)325        self.assertEqual(bytes(m), textwrap.dedent("""\326            Content-Type: text/plain; charset="utf-8"327            Content-Transfer-Encoding: 8bit328            áàäéèÄöÅ.329            """).encode('utf-8'))330        self.assertEqual(m.get_payload(decode=True).decode('utf-8'), content)331        self.assertEqual(m.get_content(), content)332    def test_set_text_11_lines_maximal_non_ascii_heuristics(self):333        m = self._make_message()334        content = '\n'*10 + "áàäéèÄöÅ.\n"335        raw_data_manager.set_content(m, content)336        self.assertEqual(bytes(m), textwrap.dedent("""\337            Content-Type: text/plain; charset="utf-8"338            Content-Transfer-Encoding: 8bit339            """ + '\n'*10 + """340            áàäéèÄöÅ.341            """).encode('utf-8'))342        self.assertEqual(m.get_payload(decode=True).decode('utf-8'), content)343        self.assertEqual(m.get_content(), content)344    def test_set_text_long_line_maximal_non_ascii_heuristics(self):345        m = self._make_message()346        content = ("áàäéèÄöÅáàäéèÄöÅáàäéèÄöÅáàäéèÄöÅ"347                   "áàäéèÄöÅáàäéèÄöÅáàäéèÄöÅáàäéèÄöÅ"348                   "áàäéèÄöÅáàäéèÄöÅáàäéèÄöÅáàäéèÄöÅ.\n")349        raw_data_manager.set_content(m, content)350        self.assertEqual(bytes(m), textwrap.dedent("""\351            Content-Type: text/plain; charset="utf-8"352            Content-Transfer-Encoding: base64353            w6HDoMOkw6nDqMSZw7bFkcOhw6DDpMOpw6jEmcO2xZHDocOgw6TDqcOoxJnD354            tsWRw6HDoMOkw6nDqMSZw7bFkcOhw6DDpMOpw6jEmcO2xZHDocOgw6TDqcOo355            xJnDtsWRw6HDoMOkw6nDqMSZw7bFkcOhw6DDpMOpw6jEmcO2xZHDocOgw6TD356            qcOoxJnDtsWRw6HDoMOkw6nDqMSZw7bFkcOhw6DDpMOpw6jEmcO2xZHDocOg357            w6TDqcOoxJnDtsWRLgo=358            """).encode('utf-8'))359        self.assertEqual(m.get_payload(decode=True).decode('utf-8'), content)360        self.assertEqual(m.get_content(), content)361    def test_set_text_11_lines_long_line_maximal_non_ascii_heuristics(self):362        # Yes, it chooses "wrong" here.  It's a heuristic.  So this result363        # could change if we come up with a better heuristic.364        m = self._make_message()365        content = ('\n'*10 +366                   "áàäéèÄöÅáàäéèÄöÅáàäéèÄöÅáàäéèÄöÅ"367                   "áàäéèÄöÅáàäéèÄöÅáàäéèÄöÅáàäéèÄöÅ"368                   "áàäéèÄöÅáàäéèÄöÅáàäéèÄöÅáàäéèÄöÅ.\n")369        raw_data_manager.set_content(m, "\n"*10 +370                                        "áàäéèÄöÅáàäéèÄöÅáàäéèÄöÅáàäéèÄöÅ"371                                        "áàäéèÄöÅáàäéèÄöÅáàäéèÄöÅáàäéèÄöÅ"372                                        "áàäéèÄöÅáàäéèÄöÅáàäéèÄöÅáàäéèÄöÅ.\n")373        self.assertEqual(bytes(m), textwrap.dedent("""\374            Content-Type: text/plain; charset="utf-8"375            Content-Transfer-Encoding: quoted-printable376            """ + '\n'*10 + """377            =C3=A1=C3=A0=C3=A4=C3=A9=C3=A8=C4=99=C3=B6=C5=91=C3=A1=C3=378            =A0=C3=A4=C3=A9=C3=A8=C4=99=C3=B6=C5=91=C3=A1=C3=A0=C3=A4=379            =C3=A9=C3=A8=C4=99=C3=B6=C5=91=C3=A1=C3=A0=C3=A4=C3=A9=C3=380            =A8=C4=99=C3=B6=C5=91=C3=A1=C3=A0=C3=A4=C3=A9=C3=A8=C4=99=381            =C3=B6=C5=91=C3=A1=C3=A0=C3=A4=C3=A9=C3=A8=C4=99=C3=B6=C5=382            =91=C3=A1=C3=A0=C3=A4=C3=A9=C3=A8=C4=99=C3=B6=C5=91=C3=A1=383            =C3=A0=C3=A4=C3=A9=C3=A8=C4=99=C3=B6=C5=91=C3=A1=C3=A0=C3=384            =A4=C3=A9=C3=A8=C4=99=C3=B6=C5=91=C3=A1=C3=A0=C3=A4=C3=A9=385            =C3=A8=C4=99=C3=B6=C5=91=C3=A1=C3=A0=C3=A4=C3=A9=C3=A8=C4=386            =99=C3=B6=C5=91=C3=A1=C3=A0=C3=A4=C3=A9=C3=A8=C4=99=C3=B6=387            =C5=91.388            """).encode('utf-8'))389        self.assertEqual(m.get_payload(decode=True).decode('utf-8'), content)390        self.assertEqual(m.get_content(), content)391    def test_set_text_non_ascii_with_cte_7bit_raises(self):392        m = self._make_message()393        with self.assertRaises(UnicodeError):394            raw_data_manager.set_content(m,"áàäéèÄöÅ.\n", cte='7bit')395    def test_set_text_non_ascii_with_charset_ascii_raises(self):396        m = self._make_message()397        with self.assertRaises(UnicodeError):398            raw_data_manager.set_content(m,"áàäéèÄöÅ.\n", charset='ascii')399    def test_set_text_non_ascii_with_cte_7bit_and_charset_ascii_raises(self):400        m = self._make_message()401        with self.assertRaises(UnicodeError):402            raw_data_manager.set_content(m,"áàäéèÄöÅ.\n", cte='7bit', charset='ascii')403    def test_set_message(self):404        m = self._make_message()405        m['Subject'] = "Forwarded message"406        content = self._make_message()407        content['To'] = 'python@vivarium.org'408        content['From'] = 'police@monty.org'409        content['Subject'] = "get back in your box"410        content.set_content("Or face the comfy chair.")411        raw_data_manager.set_content(m, content)412        self.assertEqual(str(m), textwrap.dedent("""\413            Subject: Forwarded message414            Content-Type: message/rfc822415            Content-Transfer-Encoding: 8bit416            To: python@vivarium.org417            From: police@monty.org418            Subject: get back in your box419            Content-Type: text/plain; charset="utf-8"420            Content-Transfer-Encoding: 7bit421            MIME-Version: 1.0422            Or face the comfy chair.423            """))424        payload = m.get_payload(0)425        self.assertIsInstance(payload, self.message)426        self.assertEqual(str(payload), str(content))427        self.assertIsInstance(m.get_content(), self.message)428        self.assertEqual(str(m.get_content()), str(content))429    def test_set_message_with_non_ascii_and_coercion_to_7bit(self):430        m = self._make_message()431        m['Subject'] = "Escape report"432        content = self._make_message()433        content['To'] = 'police@monty.org'434        content['From'] = 'victim@monty.org'435        content['Subject'] = "Help"436        content.set_content("j'ai un problème de python. il est sorti de son"437                            " vivarium.")438        raw_data_manager.set_content(m, content)439        self.assertEqual(bytes(m), textwrap.dedent("""\440            Subject: Escape report441            Content-Type: message/rfc822442            Content-Transfer-Encoding: 8bit443            To: police@monty.org444            From: victim@monty.org445            Subject: Help446            Content-Type: text/plain; charset="utf-8"447            Content-Transfer-Encoding: 8bit448            MIME-Version: 1.0449            j'ai un problème de python. il est sorti de son vivarium.450            """).encode('utf-8'))451        # The choice of base64 for the body encoding is because generator452        # doesn't bother with heuristics and uses it unconditionally for utf-8453        # text.454        # XXX: the first cte should be 7bit, too...that's a generator bug.455        # XXX: the line length in the body also looks like a generator bug.456        self.assertEqual(m.as_string(maxheaderlen=self.policy.max_line_length),457                         textwrap.dedent("""\458            Subject: Escape report459            Content-Type: message/rfc822460            Content-Transfer-Encoding: 8bit461            To: police@monty.org462            From: victim@monty.org463            Subject: Help464            Content-Type: text/plain; charset="utf-8"465            Content-Transfer-Encoding: base64466            MIME-Version: 1.0467            aidhaSB1biBwcm9ibMOobWUgZGUgcHl0aG9uLiBpbCBlc3Qgc29ydGkgZGUgc29uIHZpdmFyaXVt468            Lgo=469            """))470        self.assertIsInstance(m.get_content(), self.message)471        self.assertEqual(str(m.get_content()), str(content))472    def test_set_message_invalid_cte_raises(self):473        m = self._make_message()474        content = self._make_message()475        for cte in 'quoted-printable base64'.split():476            for subtype in 'rfc822 external-body'.split():477                with self.subTest(cte=cte, subtype=subtype):478                    with self.assertRaises(ValueError) as ar:479                        m.set_content(content, subtype, cte=cte)480                    exc = str(ar.exception)481                    self.assertIn(cte, exc)482                    self.assertIn(subtype, exc)483        subtype = 'external-body'484        for cte in '8bit binary'.split():485            with self.subTest(cte=cte, subtype=subtype):486                with self.assertRaises(ValueError) as ar:487                    m.set_content(content, subtype, cte=cte)488                exc = str(ar.exception)489                self.assertIn(cte, exc)490                self.assertIn(subtype, exc)491    def test_set_image_jpg(self):492        for content in (b"bogus content",493                        bytearray(b"bogus content"),494                        memoryview(b"bogus content")):495            with self.subTest(content=content):496                m = self._make_message()497                raw_data_manager.set_content(m, content, 'image', 'jpeg')498                self.assertEqual(str(m), textwrap.dedent("""\499                    Content-Type: image/jpeg500                    Content-Transfer-Encoding: base64501                    Ym9ndXMgY29udGVudA==502                    """))503                self.assertEqual(m.get_payload(decode=True), content)504                self.assertEqual(m.get_content(), content)505    def test_set_audio_aif_with_quoted_printable_cte(self):506        # Why you would use qp, I don't know, but it is technically supported.507        # XXX: the incorrect line length is because binascii.b2a_qp doesn't508        # support a line length parameter, but we must use it to get newline509        # encoding.510        # XXX: what about that lack of tailing newline?  Do we actually handle511        # that correctly in all cases?  That is, if the *source* has an512        # unencoded newline, do we add an extra newline to the returned payload513        # or not?  And can that actually be disambiguated based on the RFC?514        m = self._make_message()515        content = b'b\xFFgus\tcon\nt\rent ' + b'z'*100516        m.set_content(content, 'audio', 'aif', cte='quoted-printable')517        self.assertEqual(bytes(m), textwrap.dedent("""\518            Content-Type: audio/aif519            Content-Transfer-Encoding: quoted-printable520            MIME-Version: 1.0521            b=FFgus=09con=0At=0Dent=20zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz=522            zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz""").encode('latin-1'))523        self.assertEqual(m.get_payload(decode=True), content)524        self.assertEqual(m.get_content(), content)525    def test_set_video_mpeg_with_binary_cte(self):526        m = self._make_message()527        content = b'b\xFFgus\tcon\nt\rent ' + b'z'*100528        m.set_content(content, 'video', 'mpeg', cte='binary')529        self.assertEqual(bytes(m), textwrap.dedent("""\530            Content-Type: video/mpeg531            Content-Transfer-Encoding: binary532            MIME-Version: 1.0533            """).encode('ascii') +534            # XXX: the second \n ought to be a \r, but generator gets it wrong.535            # THIS MEANS WE DON'T ACTUALLY SUPPORT THE 'binary' CTE.536            b'b\xFFgus\tcon\nt\nent zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz' +537            b'zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz')538        self.assertEqual(m.get_payload(decode=True), content)539        self.assertEqual(m.get_content(), content)540    def test_set_application_octet_stream_with_8bit_cte(self):541        # In 8bit mode, univeral line end logic applies.  It is up to the542        # application to make sure the lines are short enough; we don't check.543        m = self._make_message()544        content = b'b\xFFgus\tcon\nt\rent\n' + b'z'*60 + b'\n'545        m.set_content(content, 'application', 'octet-stream', cte='8bit')546        self.assertEqual(bytes(m), textwrap.dedent("""\547            Content-Type: application/octet-stream548            Content-Transfer-Encoding: 8bit549            MIME-Version: 1.0550            """).encode('ascii') +551            b'b\xFFgus\tcon\nt\nent\n' +552            b'zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz\n')553        self.assertEqual(m.get_payload(decode=True), content)554        self.assertEqual(m.get_content(), content)555    def test_set_headers_from_header_objects(self):556        m = self._make_message()557        content = "Simple message.\n"558        header_factory = self.policy.header_factory559        raw_data_manager.set_content(m, content, headers=(560            header_factory("To", "foo@example.com"),561            header_factory("From", "foo@example.com"),562            header_factory("Subject", "I'm talking to myself.")))563        self.assertEqual(str(m), textwrap.dedent("""\564            Content-Type: text/plain; charset="utf-8"565            To: foo@example.com566            From: foo@example.com567            Subject: I'm talking to myself.568            Content-Transfer-Encoding: 7bit569            Simple message.570            """))571    def test_set_headers_from_strings(self):572        m = self._make_message()573        content = "Simple message.\n"574        raw_data_manager.set_content(m, content, headers=(575            "X-Foo-Header: foo",576            "X-Bar-Header: bar",))577        self.assertEqual(str(m), textwrap.dedent("""\578            Content-Type: text/plain; charset="utf-8"579            X-Foo-Header: foo580            X-Bar-Header: bar581            Content-Transfer-Encoding: 7bit582            Simple message.583            """))584    def test_set_headers_with_invalid_duplicate_string_header_raises(self):585        m = self._make_message()586        content = "Simple message.\n"587        with self.assertRaisesRegex(ValueError, 'Content-Type'):588            raw_data_manager.set_content(m, content, headers=(589                "Content-Type: foo/bar",)590                )591    def test_set_headers_with_invalid_duplicate_header_header_raises(self):592        m = self._make_message()593        content = "Simple message.\n"594        header_factory = self.policy.header_factory595        with self.assertRaisesRegex(ValueError, 'Content-Type'):596            raw_data_manager.set_content(m, content, headers=(597                header_factory("Content-Type", " foo/bar"),)598                )599    def test_set_headers_with_defective_string_header_raises(self):600        m = self._make_message()601        content = "Simple message.\n"602        with self.assertRaisesRegex(ValueError, 'a@fairly@@invalid@address'):603            raw_data_manager.set_content(m, content, headers=(604                'To: a@fairly@@invalid@address',)605                )606            print(m['To'].defects)607    def test_set_headers_with_defective_header_header_raises(self):608        m = self._make_message()609        content = "Simple message.\n"610        header_factory = self.policy.header_factory611        with self.assertRaisesRegex(ValueError, 'a@fairly@@invalid@address'):612            raw_data_manager.set_content(m, content, headers=(613                header_factory('To', 'a@fairly@@invalid@address'),)614                )615            print(m['To'].defects)616    def test_set_disposition_inline(self):617        m = self._make_message()618        m.set_content('foo', disposition='inline')619        self.assertEqual(m['Content-Disposition'], 'inline')620    def test_set_disposition_attachment(self):621        m = self._make_message()622        m.set_content('foo', disposition='attachment')623        self.assertEqual(m['Content-Disposition'], 'attachment')624    def test_set_disposition_foo(self):625        m = self._make_message()626        m.set_content('foo', disposition='foo')627        self.assertEqual(m['Content-Disposition'], 'foo')628    # XXX: we should have a 'strict' policy mode (beyond raise_on_defect) that629    # would cause 'foo' above to raise.630    def test_set_filename(self):631        m = self._make_message()632        m.set_content('foo', filename='bar.txt')633        self.assertEqual(m['Content-Disposition'],634                         'attachment; filename="bar.txt"')635    def test_set_filename_and_disposition_inline(self):636        m = self._make_message()637        m.set_content('foo', disposition='inline', filename='bar.txt')638        self.assertEqual(m['Content-Disposition'], 'inline; filename="bar.txt"')639    def test_set_non_ascii_filename(self):640        m = self._make_message()641        m.set_content('foo', filename='ábárî.txt')642        self.assertEqual(bytes(m), textwrap.dedent("""\643            Content-Type: text/plain; charset="utf-8"644            Content-Transfer-Encoding: 7bit645            Content-Disposition: attachment;646             filename*=utf-8''%C3%A1b%C3%A1r%C3%AE.txt647            MIME-Version: 1.0648            foo649            """).encode('ascii'))650    content_object_params = {651        'text_plain': ('content', ()),652        'text_html': ('content', ('html',)),653        'application_octet_stream': (b'content',654                                     ('application', 'octet_stream')),655        'image_jpeg': (b'content', ('image', 'jpeg')),656        'message_rfc822': (message(), ()),657        'message_external_body': (message(), ('external-body',)),658        }659    def content_object_as_header_receiver(self, obj, mimetype):660        m = self._make_message()661        m.set_content(obj, *mimetype, headers=(662            'To: foo@example.com',663            'From: bar@simple.net'))664        self.assertEqual(m['to'], 'foo@example.com')665        self.assertEqual(m['from'], 'bar@simple.net')666    def content_object_as_disposition_inline_receiver(self, obj, mimetype):667        m = self._make_message()668        m.set_content(obj, *mimetype, disposition='inline')669        self.assertEqual(m['Content-Disposition'], 'inline')670    def content_object_as_non_ascii_filename_receiver(self, obj, mimetype):671        m = self._make_message()672        m.set_content(obj, *mimetype, disposition='inline', filename='bár.txt')673        self.assertEqual(m['Content-Disposition'], 'inline; filename="bár.txt"')674        self.assertEqual(m.get_filename(), "bár.txt")675        self.assertEqual(m['Content-Disposition'].params['filename'], "bár.txt")676    def content_object_as_cid_receiver(self, obj, mimetype):677        m = self._make_message()678        m.set_content(obj, *mimetype, cid='some_random_stuff')679        self.assertEqual(m['Content-ID'], 'some_random_stuff')680    def content_object_as_params_receiver(self, obj, mimetype):681        m = self._make_message()682        params = {'foo': 'bár', 'abc': 'xyz'}683        m.set_content(obj, *mimetype, params=params)684        if isinstance(obj, str):685            params['charset'] = 'utf-8'686        self.assertEqual(m['Content-Type'].params, params)687if __name__ == '__main__':...default.py
Source:default.py  
1#!/usr/bin/python2# -*- coding: utf-8 -*-3import xbmc, xbmcgui, xbmcaddon, urllib, urllib2, socket, cookielib, re, os, shutil, base64, xbmcvfs4addon = xbmcaddon.Addon()5socket.setdefaulttimeout(60)6addonID = addon.getAddonInfo('id')7translation = addon.getLocalizedString8addonUserDataFolder=xbmc.translatePath("special://profile/addon_data/"+addonID)9subTempDir=xbmc.translatePath("special://profile/addon_data/"+addonID+"/srtTemp/")10icon = xbmc.translatePath('special://home/addons/'+addonID+'/icon.png')11rarFile=xbmc.translatePath(addonUserDataFolder+"/sub.")12subFile=xbmc.translatePath(addonUserDataFolder+"/sub.srt")13favFile=xbmc.translatePath(addonUserDataFolder+"/favourites")14apiKeyFile=xbmc.translatePath(addonUserDataFolder+"/api.key")15if not os.path.isdir(addonUserDataFolder):16  os.mkdir(addonUserDataFolder)17if not os.path.isdir(subTempDir):18  os.mkdir(subTempDir)19if os.path.exists(apiKeyFile):20  fh = open(apiKeyFile, 'r')21  ownKey = fh.read()22  fh.close()23else:24  ownKey=""25user=""26pw=""27backNav=""28pause=""29saveSub=""30language=""31def getSettings():32  global user33  user=addon.getSetting("user")34  global pw35  pw=addon.getSetting("pw")36  global backNav37  backNav=addon.getSetting("backNav")38  global pause39  pause=addon.getSetting("pause")40  global saveSub41  saveSub=addon.getSetting("saveSub")42  global language43  language=addon.getSetting("language")44getSettings()45if pause=="true" and xbmc.Player().isPlayingVideo():46  xbmc.Player().pause()47playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)48if playlist.getposition()>=0:49  currentTitle = playlist[playlist.getposition()].getdescription()50  51currentFile = xbmc.Player().getPlayingFile()52cj = cookielib.CookieJar()53mainUrl = "http://www.subcentral.de"54opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))55userAgent = "Mozilla/5.0 (Windows NT 6.2; WOW64; rv:23.0) Gecko/20100101 Firefox/23.0"56opener.addheaders = [('User-Agent', userAgent)]57opener.open(mainUrl+"/index.php?form=UserLogin", data="loginUsername="+urllib.quote_plus(user)+"&loginPassword="+urllib.quote_plus(pw))58while (user=="" or pw==""):59  addon.openSettings()60  getSettings()61currentEpisode=""62currentSeason=""63currentEpisode=xbmc.getInfoLabel('VideoPlayer.Episode')64currentSeason=xbmc.getInfoLabel('VideoPlayer.Season')65dirName = ""66try:67  dirName=(currentFile.split(os.sep)[-2]).lower()68except:69  pass70fileName=os.path.basename(currentFile).lower()71if currentEpisode=="":72  matchDir=re.compile('\\.s(.+?)e(.+?)\\.', re.DOTALL).findall(dirName)73  matchFile=re.compile('\\.s(.+?)e(.+?)\\.', re.DOTALL).findall(fileName)74  if len(matchDir)>0:75    currentSeason=matchDir[0][0]76    currentEpisode=matchDir[0][1]77  elif len(matchFile)>0:78    currentSeason=matchFile[0][0]79    currentEpisode=matchFile[0][1]80if currentEpisode=="":81  match=re.compile('(.+?)- s(.+?)e(.+?) ', re.DOTALL).findall(xbmc.getInfoLabel('VideoPlayer.Title').lower())82  if len(match)>0:83    currentSeason=match[0][1]84    currentEpisode=match[0][2]85if len(currentEpisode)==1:86  currentEpisode="0"+currentEpisode87if len(currentSeason)==1:88  currentSeason="0"+currentSeason89currentRelease=""90if "-" in fileName:91  currentRelease=(fileName.split("-")[-1]).lower()92  if "." in currentRelease:93    currentRelease=currentRelease[:currentRelease.find(".")]94elif "-" in dirName:95  currentRelease=(dirName.split("-")[-1]).lower()96def main():97        global mainType98        mainType = ""99        dialog = xbmcgui.Dialog()100        nr=dialog.select("SubCentral.de", [translation(30013),translation(30001),translation(30002),translation(30011)])101        if nr==0:102          search()103        elif nr==1:104          mainType="fav"105          showFavourites()106        elif nr==2:107          mainType="all"108          showAllSeries()109        elif nr==3:110          addon.openSettings()111          getSettings()112          main()113        else:114          if pause=="true" and xbmc.Player().isPlayingVideo():115            xbmc.Player().pause()116def search():117        title=xbmc.getInfoLabel('VideoPlayer.TVShowTitle')118        season=currentSeason119        episode=currentEpisode120        release=currentRelease121        122        if title=="" or season=="":123          matchDir=re.compile('(.+?)\\.s(.+?)e(.+?)\\.', re.DOTALL).findall(dirName)124          matchFile=re.compile('(.+?)\\.s(.+?)e(.+?)\\.', re.DOTALL).findall(fileName)125          matchTitle=re.compile('(.+?)- s(.+?)e(.+?) ', re.DOTALL).findall(xbmc.getInfoLabel('VideoPlayer.Title').lower())126          if len(matchDir)>0:127            title=matchDir[0][0]128          elif len(matchFile)>0:129            title=matchFile[0][0]130          elif len(matchTitle)>0:131            title=matchTitle[0][0].strip()132        133        title=title.replace("."," ")134        if "(" in title:135          title=title[:title.find("(")].strip()136        137        if title=="" or season=="" or episode=="":138          xbmc.executebuiltin('XBMC.Notification(SubCentral.de:,'+translation(30014)+'!,3000,'+icon+')')139          main()140        else:141          if season[0:1]=="0":142            season=season[1:]143          general=base64.b64decode("QUl6YVN5RGJtNzRTNlZia1VjWmNQeC1HTTFtU1B5N2ZYU0R2Vy1J")144          global ownKey145          if ownKey=="":146            ownKey=general147          searchString='intitle:"[subs] '+title+' - staffel '+season+'|0'+season+'"'148          fullUrl="https://www.googleapis.com/customsearch/v1?key="+ownKey+"&cx=016144106520845837387:lj4yjfpwyna&q="+urllib.quote_plus(searchString)+"&alt=json"149          xbmc.log("SCDE Addon Log - Search: "+title+"#"+season)150          content = getUrl(fullUrl)151          if '"items"' in content:152            content = content[content.find('"items"'):]153            spl=content.split('"kind"')154            finalUrl=""155            for i in range(1,len(spl),1):156              entry=spl[i]157              match=re.compile('"title": "(.+?)"', re.DOTALL).findall(entry)158              title=match[0]159              match=re.compile('"link": "(.+?)"', re.DOTALL).findall(entry)160              url=match[0]161              if "staffel" in title.lower() and "subs:" in title.lower() and "postID=" not in url and "boardID=" not in url:162                finalUrl=url163                break164            if finalUrl=="":165              for i in range(1,len(spl),1):166                entry=spl[i]167                match=re.compile('"title": "(.+?)"', re.DOTALL).findall(entry)168                title=match[0]169                match=re.compile('"link": "(.+?)"', re.DOTALL).findall(entry)170                url=match[0]171                if "staffel" in title.lower() and "postID=" not in url and "boardID=" not in url:172                  finalUrl=url173                  break174            175            if len(season)==1:176              season="0"+season177            178            if finalUrl!="":179              threadID = finalUrl[finalUrl.find("&threadID=")+10:]180              if "&" in threadID:181                  threadID = threadID[:threadID.find("&")]182              content = opener.open("http://www.subcentral.de/index.php?page=Thread&threadID="+threadID).read()183              if 'class="bedankhinweisSC' in content:184                contentThanks=content185                contentThanks=contentThanks[contentThanks.find('class="bedankhinweisSC'):]186                match=re.compile('href="index.php\\?action=Thank(.+?)"', re.DOTALL).findall(contentThanks)187                matchID=re.compile('name="threadID" value="(.+?)"', re.DOTALL).findall(contentThanks)188                dialog = xbmcgui.Dialog()189                nr=dialog.select(title, [translation(30005)+"..."])190                if nr>=0:191                  opener.open(mainUrl+"/index.php?action=Thank"+match[0].replace("&","&"))192                  content = opener.open(mainUrl+"/index.php?page=Thread&threadID="+matchID[0]).read()193              194              attachments = []195              titles = []196              197              match=re.compile('<title>(.+?)-', re.DOTALL).findall(content)198              tvShowTitle=match[0].replace("[Subs]","").strip()199              200              content = content[content.find("quoteData.set('post-")+1:]201              content = content[:content.find("quoteData.set('post-")]202              contentDE=content203              contentEN=""204              if '<img src="creative/bilder/flags/usa.png"' in content:205                contentDE=content[:content.find('<img src="creative/bilder/flags/usa.png"')]206                contentEN=content[content.find('<img src="creative/bilder/flags/usa.png"'):]207              elif '<img src="creative/bilder/flags/uk.png"' in content:208                contentDE=content[:content.find('<img src="creative/bilder/flags/uk.png"')]209                contentEN=content[content.find('<img src="creative/bilder/flags/uk.png"'):]210              elif 'Englische Untertitel:' in content:211                contentDE=content[:content.find('Englische Untertitel:')]212                contentEN=content[content.find('Englische Untertitel:'):]213              if "page=Attachment&attachmentID=" not in contentDE:214                contentDE=content215              216              if language=="0":217                tempDE=appendSubInfo(tvShowTitle,season,episode,release,contentDE,"DE")218                attachments += tempDE[0]219                titles += tempDE[1]220                if contentEN!="":221                  tempEN=appendSubInfo(tvShowTitle,season,episode,release,contentEN,"EN")222                  attachments += tempEN[0]223                  titles += tempEN[1]224              elif language=="1":225                tempDE=appendSubInfo(tvShowTitle,season,episode,release,contentDE,"DE")226                attachments += tempDE[0]227                titles += tempDE[1]228              elif language=="2" and contentEN!="":229                tempEN=appendSubInfo(tvShowTitle,season,episode,release,contentEN,"EN")230                attachments += tempEN[0]231                titles += tempEN[1]232              233              if len(titles)>0:234                titles, attachments = (list(x) for x in zip(*sorted(zip(titles, attachments))))235              dialog = xbmcgui.Dialog()236              nr=dialog.select(os.path.basename(currentFile), titles)237              if nr>=0:238                subUrl=mainUrl+"/index.php?page=Attachment&attachmentID="+attachments[nr]239                setSubtitle(subUrl)240              elif backNav=="true":241                main()242            else:243              xbmc.executebuiltin('XBMC.Notification(SubCentral.de:,'+translation(30015)+'!,3000,'+icon+')')244              main()245          elif '"totalResults": "0"' in content:246            xbmc.executebuiltin('XBMC.Notification(SubCentral.de:,'+translation(30015)+'!,3000,'+icon+')')247            main()248          else:249            xbmc.executebuiltin('XBMC.Notification(SubCentral.de:,'+translation(30016)+'!,10000,'+icon+')')250            main()251def getEpisodes(entry):252        ep=ep2=""253        match=re.compile('>E(.+?) -', re.DOTALL).findall(entry,0,10)254        match2=re.compile('>(.+?)x(.+?) -', re.DOTALL).findall(entry,0,10)255        match3=re.compile('>(.+?)\\. ', re.DOTALL).findall(entry,0,10)256        match4=re.compile('>(.+?) -', re.DOTALL).findall(entry,0,10)257        match5=re.compile('>(.+?)<', re.DOTALL).findall(entry,0,10)258        if "- komplett<" in entry.lower():259          ep=ep2=" - Komplett"260        else:261          if len(match)>0:262            ep=match[0]263          elif len(match2):264            ep=match2[0][1]265          elif len(match3):266            ep=match3[0]267          elif len(match4):268            ep=match4[0]269          elif len(match5):270            ep=match5[0]271          else:272            ep="00"273          ep2="E"+ep274        return [ep,ep2]275def appendSubInfo(tvShowTitle,season,episode,release,content,lang):276        attachments1 = []277        titles1 = []278        content2=content.replace('<span class="  Stil36 Stil31">','').replace('<span class="Stil37">','')279        match=re.compile('<div align="center"><a href="http://www.subcentral.de/index.php\\?page=Attachment&attachmentID=(.+?)">⪠E(.+?) \\((.+?)\\)</a>', re.DOTALL).findall(content2)280        for attach, ep, rel in match:281          ep2="E"+ep282          if episode==ep:283            check = release==""284            if release!="":285              check = release==rel.lower()286            if check:287              if attach not in attachments1:288                attachments1.append(attach)289                titles1.append(lang+" - "+tvShowTitle+" - S"+season+ep2+" - "+rel.replace("</span>",""))290        if len(attachments1)==0:291          match=re.compile('<div align="center"><a href="http://www.subcentral.de/index.php\\?page=Attachment&attachmentID=(.+?)">⪠E(.+?) \\((.+?)\\)</a>', re.DOTALL).findall(content2)292          for attach, ep, rel in match:293            ep2="E"+ep294            if episode==ep:295              if attach not in attachments1:296                attachments1.append(attach)297                titles1.append(lang+" - "+tvShowTitle+" - S"+season+ep2+" - "+rel.replace("</span>",""))298        if len(attachments1)==0:299          match=re.compile('<div align="center"><a href="http://www.subcentral.de/index.php\\?page=Attachment&attachmentID=(.+?)">⪠E(.+?) \\((.+?)\\)</a>', re.DOTALL).findall(content2)300          for attach, ep, rel in match:301            ep2="E"+ep302            if attach not in attachments1:303              attachments1.append(attach)304              titles1.append(lang+" - "+tvShowTitle+" - S"+season+ep2+" - "+rel.replace("</span>",""))305        attachments2 = []306        titles2 = []307        splitStr = ""308        if 'class="release"' in content:309          splitStr = 'class="release"'310        elif 'class="Stil9"' in content:311          splitStr = 'class="Stil9"'312        if splitStr:313          spl=content.split(splitStr)314          for i in range(1,len(spl),1):315            entry=spl[i].replace("<strong>","").replace("</strong>","").replace('<span style="font-size: 8pt">','')316            temp = getEpisodes(entry)317            ep = temp[0]318            ep2 = temp[1]319            match=re.compile('index.php\\?page=Attachment&attachmentID=(.+?)">(.+?)</a>', re.DOTALL).findall(entry)320            for attach, rel in match:321              if episode==ep:322                check = release==""323                if release!="":324                  check = release==rel.lower()325                if check:326                  if attach not in attachments2:327                    attachments2.append(attach)328                    titles2.append(lang+" - "+tvShowTitle+" - S"+season+ep2+" - "+rel.replace("</span>",""))329          if len(attachments2)==0:330            spl=content.split(splitStr)331            for i in range(1,len(spl),1):332              entry=spl[i].replace("<strong>","").replace("</strong>","").replace('<span style="font-size: 8pt">','')333              temp = getEpisodes(entry)334              ep = temp[0]335              ep2 = temp[1]336              match=re.compile('index.php\\?page=Attachment&attachmentID=(.+?)">(.+?)</a>', re.DOTALL).findall(entry)337              for attach, rel in match:338                if episode==ep:339                  if attach not in attachments2:340                    attachments2.append(attach)341                    titles2.append(lang+" - "+tvShowTitle+" - S"+season+ep2+" - "+rel.replace("</span>",""))342          if len(attachments2)==0:343            spl=content.split(splitStr)344            for i in range(1,len(spl),1):345              entry=spl[i].replace("<strong>","").replace("</strong>","").replace('<span style="font-size: 8pt">','')346              temp = getEpisodes(entry)347              ep = temp[0]348              ep2 = temp[1]349              match=re.compile('index.php\\?page=Attachment&attachmentID=(.+?)">(.+?)</a>', re.DOTALL).findall(entry)350              for attach, rel in match:351                if attach not in attachments2:352                  attachments2.append(attach)353                  titles2.append(lang+" - "+tvShowTitle+" - S"+season+ep2+" - "+rel.replace("</span>",""))354        return [attachments1+attachments2,titles1+titles2]355def showFavourites():356        ids = []357        titles = []358        if os.path.exists(favFile):359          fh = open(favFile, 'r')360          for line in fh:361            id = line[:line.find("#")]362            title = line[line.find("#")+1:]363            title = title[:title.find("#END")]364            ids.append(id)365            titles.append(title)366          fh.close()367        titles, ids = (list(x) for x in zip(*sorted(zip(titles, ids))))368        dialog = xbmcgui.Dialog()369        nr=dialog.select(translation(30001), titles)370        if nr>=0:371          id=ids[nr]372          title=titles[nr]373          showSeries(id)374        elif backNav=="true":375          main()376def showAllSeries():377        content = opener.open(mainUrl+"/index.php").read()378        content = content[content.find('<option value=""> Serien QuickJump </option>')+1:]379        content = content[:content.find('</form>')]380        match=re.compile('<option value="(.+?)">(.+?)</option>', re.DOTALL).findall(content)381        threadIDs = []382        threadNames = []383        for id, title in match:384          threadIDs.append(id)385          threadNames.append(title)386        dialog = xbmcgui.Dialog()387        nr=dialog.select(translation(30002), threadNames)388        if nr>=0:389          id=threadIDs[nr]390          title=threadNames[nr]391          showSeries(id)392        elif backNav=="true":393          main()394def showSeries(seriesID):395        content = opener.open(mainUrl+"/index.php?page=Board&boardID="+seriesID).read()396        match=re.compile('<title>(.+?) -', re.DOTALL).findall(content)397        SeriesTitle=match[0]398        content = content[content.find("<h3>Wichtige Themen</h3>"):]399        content = content[:content.find('</table>')]400        spl=content.split('<p id="threadTitle')401        threadIDs = []402        threadNames = []403        season=currentSeason404        if season[0:1]=="0":405          season=season[1:]406        for i in range(1,len(spl),1):407          entry=spl[i]408          match=re.compile('<a href="index.php\\?page=Thread&threadID=(.+?)">(.+?)</a>', re.DOTALL).findall(entry)409          if ("staffel "+season in match[0][1].lower() or "staffel 0"+season in match[0][1].lower()) and "subs" in match[0][1].lower():410            threadIDs.append(match[0][0])411            threadNames.append(cleanTitle(match[0][1]))412        if len(threadIDs)==0:413          for i in range(1,len(spl),1):414            entry=spl[i]415            match=re.compile('<a href="index.php\\?page=Thread&threadID=(.+?)">(.+?)</a>', re.DOTALL).findall(entry)416            if "subs" in match[0][1].lower():417              threadIDs.append(match[0][0])418              threadNames.append(cleanTitle(match[0][1]))419        threadNames, threadIDs = (list(x) for x in zip(*sorted(zip(threadNames, threadIDs))))420        content=""421        if os.path.exists(favFile):422          fh = open(favFile, 'r')423          content=fh.read()424          fh.close()425        if seriesID+"#" not in content:426          threadNames.append(translation(30003))427        else:428          threadNames.append(translation(30004))429        dialog = xbmcgui.Dialog()430        nr=dialog.select(os.path.basename(currentFile), threadNames)431        if nr>=0:432          if nr==len(threadNames)-1:433            if threadNames[nr]==translation(30003):434              addToFavourites(seriesID,SeriesTitle)435            elif threadNames[nr]==translation(30004):436              removeFromFavourites(seriesID,SeriesTitle)437            showSeries(seriesID)438          else:439            id=threadIDs[nr]440            showSubtitles(seriesID,id)441        elif backNav=="true":442          if mainType=="all":443            showAllSeries()444          elif mainType=="fav":445            showFavourites()446def showSubtitles(seriesID,id):447        content = opener.open(mainUrl+"/index.php?page=Thread&threadID="+id).read()448        match=re.compile('<title>(.+?)</title>', re.DOTALL).findall(content)449        title=match[0]450        if 'class="bedankhinweisSC' in content:451          contentThanks=content452          contentThanks=contentThanks[contentThanks.find('class="bedankhinweisSC'):]453          match=re.compile('href="index.php\\?action=Thank(.+?)"', re.DOTALL).findall(contentThanks)454          matchID=re.compile('name="threadID" value="(.+?)"', re.DOTALL).findall(contentThanks)455          dialog = xbmcgui.Dialog()456          nr=dialog.select(title, [translation(30005)+"..."])457          if nr>=0:458            opener.open(mainUrl+"/index.php?action=Thank"+match[0].replace("&","&"))459            content = opener.open(mainUrl+"/index.php?page=Thread&threadID="+id).read()460          elif backNav=="true":461            showSeries(seriesID)462        attachments = []463        titles = []464        465        match=re.compile('<title>(.+?)-', re.DOTALL).findall(content)466        tvShowTitle=match[0].replace("[Subs]","").strip()467        match=re.compile('Staffel (.+?) ', re.DOTALL).findall(content)468        season=match[0]469        if len(season)==1:470          season="0"+season471        472        content = content[content.find("quoteData.set('post-")+1:]473        content = content[:content.find("quoteData.set('post-")]474        contentDE=content475        contentEN=""476        if '<img src="creative/bilder/flags/usa.png"' in content:477          contentDE=content[:content.find('<img src="creative/bilder/flags/usa.png"')]478          contentEN=content[content.find('<img src="creative/bilder/flags/usa.png"'):]479        elif '<img src="creative/bilder/flags/uk.png"' in content:480          contentDE=content[:content.find('<img src="creative/bilder/flags/uk.png"')]481          contentEN=content[content.find('<img src="creative/bilder/flags/uk.png"'):]482        elif 'Englische Untertitel:' in content:483          contentDE=content[:content.find('Englische Untertitel:')]484          contentEN=content[content.find('Englische Untertitel:'):]485        if "page=Attachment&attachmentID=" not in contentDE:486          contentDE=content487        488        if language=="0":489          tempDE=appendSubInfo(tvShowTitle,season,currentEpisode,currentRelease,contentDE,"DE")490          attachments += tempDE[0]491          titles += tempDE[1]492          if contentEN!="":493            tempEN=appendSubInfo(tvShowTitle,season,currentEpisode,currentRelease,contentEN,"EN")494            attachments += tempEN[0]495            titles += tempEN[1]496        elif language=="1":497          tempDE=appendSubInfo(tvShowTitle,season,currentEpisode,currentRelease,contentDE,"DE")498          attachments += tempDE[0]499          titles += tempDE[1]500        elif language=="2" and contentEN!="":501          tempEN=appendSubInfo(tvShowTitle,season,currentEpisode,currentRelease,contentEN,"EN")502          attachments += tempEN[0]503          titles += tempEN[1]504        505        if len(titles)>0:506          titles, attachments = (list(x) for x in zip(*sorted(zip(titles, attachments))))507        dialog = xbmcgui.Dialog()508        nr=dialog.select(os.path.basename(currentFile), titles)509        if nr>=0:510          subUrl=mainUrl+"/index.php?page=Attachment&attachmentID="+attachments[nr]511          setSubtitle(subUrl)512        elif backNav=="true":513          showSeries(seriesID)514def setSubtitle(subUrl):515        clearSubTempDir()516        rarContent = opener.open(subUrl).read()517        if rarContent.startswith("Rar"):518          ext="rar"519        else:520          ext="zip"521        global rarFile522        rarFile=rarFile+ext523        fh = open(rarFile, 'wb')524        fh.write(rarContent)525        fh.close()526        xbmc.executebuiltin("XBMC.Extract("+rarFile+", "+subTempDir+")")527        xbmc.sleep(1000)528        files = os.listdir(subTempDir)529        tempFile=""530        if len(files)>1:531          dialog = xbmcgui.Dialog()532          nr=dialog.select(currentTitle, files)533          if nr>=0:534            tempFile = xbmc.translatePath(subTempDir+"/"+files[nr])535          else:536            clearSubTempDir()537            if backNav=="true":538              main()539        elif len(files)!=0:540          tempFile = xbmc.translatePath(subTempDir+"/"+files[0])541        else:542          xbmc.executebuiltin('XBMC.Notification(SubCentral.de:,'+translation(30017)+'!,3000,'+icon+')')543          if pause=="true" and xbmc.Player().isPlayingVideo():544            xbmc.Player().pause()545        if tempFile!="":546          shutil.copy2(tempFile, subFile)547          if saveSub=="true" and "http://" not in currentFile and "plugin://" not in currentFile:548            try:549              extLength = len(currentFile.split(".")[-1])550              archiveFile = currentFile[:-extLength]+"srt"551              xbmcvfs.copy(tempFile, archiveFile)552              global subFile553              subFile = archiveFile554            except:555              pass556          clearSubTempDir()557          xbmc.Player().setSubtitles(subFile)558          xbmc.executebuiltin('XBMC.Notification(SubCentral.de:,'+translation(30012)+'!,2000,'+icon+')')559          if pause=="true" and xbmc.Player().isPlayingVideo():560            xbmc.Player().pause()561def clearSubTempDir():562        files = os.listdir(subTempDir)563        for file in files:564          try:565            os.remove(xbmc.translatePath(subTempDir+"/"+file))566          except:567            pass568def addToFavourites(seriesID,title):569        entry=seriesID+"#"+title+"#END"570        if os.path.exists(favFile):571          fh = open(favFile, 'r')572          content=fh.read()573          fh.close()574          if entry not in content:575            fh=open(favFile, 'a')576            fh.write(entry+"\n")577            fh.close()578            xbmc.executebuiltin('XBMC.Notification(SubCentral.de:,'+title+': '+translation(30008)+',3000,'+icon+')')579        else:580          fh=open(favFile, 'a')581          fh.write(entry+"\n")582          fh.close()583def removeFromFavourites(seriesID,title):584        newContent=""585        fh = open(favFile, 'r')586        for line in fh:587          if seriesID+"#" not in line:588             newContent+=line589        fh.close()590        fh=open(favFile, 'w')591        fh.write(newContent)592        fh.close()593        xbmc.executebuiltin('XBMC.Notification(SubCentral.de:,'+title+': '+translation(30009)+',3000,'+icon+')')594def getUrl(url):595        req = urllib2.Request(url)596        req.add_header('User-Agent', 'Mozilla/5.0 (Windows NT 6.1; rv:23.0) Gecko/20100101 Firefox/23.0')597        response = urllib2.urlopen(req)598        content=response.read()599        response.close()600        return content601def cleanTitle(title):602        title=title.replace("<","<").replace(">",">").replace("&","&").replace("'","'").replace(""","\"").replace("ß","Ã").replace("–","-")603        title=title.replace("Ä","Ã").replace("Ü","Ã").replace("Ö","Ã").replace("ä","ä").replace("ü","ü").replace("ö","ö")604        title=title.strip()605        return title...test_message.py
Source:test_message.py  
...367        allparts = list(m.walk())368        parts = [allparts[n] for n in parts]369        self.assertEqual(list(m.iter_parts()), parts)370    class _TestContentManager:371        def get_content(self, msg, *args, **kw):372            return msg, args, kw373        def set_content(self, msg, *args, **kw):374            self.msg = msg375            self.args = args376            self.kw = kw377    def test_get_content_with_cm(self):378        m = self._str_msg('')379        cm = self._TestContentManager()380        self.assertEqual(m.get_content(content_manager=cm), (m, (), {}))381        msg, args, kw = m.get_content('foo', content_manager=cm, bar=1, k=2)382        self.assertEqual(msg, m)383        self.assertEqual(args, ('foo',))384        self.assertEqual(kw, dict(bar=1, k=2))385    def test_get_content_default_cm_comes_from_policy(self):386        p = policy.default.clone(content_manager=self._TestContentManager())387        m = self._str_msg('', policy=p)388        self.assertEqual(m.get_content(), (m, (), {}))389        msg, args, kw = m.get_content('foo', bar=1, k=2)390        self.assertEqual(msg, m)391        self.assertEqual(args, ('foo',))392        self.assertEqual(kw, dict(bar=1, k=2))393    def test_set_content_with_cm(self):394        m = self._str_msg('')395        cm = self._TestContentManager()396        m.set_content(content_manager=cm)397        self.assertEqual(cm.msg, m)398        self.assertEqual(cm.args, ())399        self.assertEqual(cm.kw, {})400        m.set_content('foo', content_manager=cm, bar=1, k=2)401        self.assertEqual(cm.msg, m)402        self.assertEqual(cm.args, ('foo',))403        self.assertEqual(cm.kw, dict(bar=1, k=2))404    def test_set_content_default_cm_comes_from_policy(self):405        cm = self._TestContentManager()406        p = policy.default.clone(content_manager=cm)407        m = self._str_msg('', policy=p)408        m.set_content()409        self.assertEqual(cm.msg, m)410        self.assertEqual(cm.args, ())411        self.assertEqual(cm.kw, {})412        m.set_content('foo', bar=1, k=2)413        self.assertEqual(cm.msg, m)414        self.assertEqual(cm.args, ('foo',))415        self.assertEqual(cm.kw, dict(bar=1, k=2))416    # outcome is whether xxx_method should raise ValueError error when called417    # on multipart/subtype.  Blank outcome means it depends on xxx (add418    # succeeds, make raises).  Note: 'none' means there are content-type419    # headers but payload is None...this happening in practice would be very420    # unusual, so treating it as if there were content seems reasonable.421    #    method          subtype           outcome422    subtype_params = (423        ('related',      'no_content',     'succeeds'),424        ('related',      'none',           'succeeds'),425        ('related',      'plain',          'succeeds'),426        ('related',      'related',        ''),427        ('related',      'alternative',    'raises'),428        ('related',      'mixed',          'raises'),429        ('alternative',  'no_content',     'succeeds'),430        ('alternative',  'none',           'succeeds'),431        ('alternative',  'plain',          'succeeds'),432        ('alternative',  'related',        'succeeds'),433        ('alternative',  'alternative',    ''),434        ('alternative',  'mixed',          'raises'),435        ('mixed',        'no_content',     'succeeds'),436        ('mixed',        'none',           'succeeds'),437        ('mixed',        'plain',          'succeeds'),438        ('mixed',        'related',        'succeeds'),439        ('mixed',        'alternative',    'succeeds'),440        ('mixed',        'mixed',          ''),441        )442    def _make_subtype_test_message(self, subtype):443        m = self.message()444        payload = None445        msg_headers =  [446            ('To', 'foo@bar.com'),447            ('From', 'bar@foo.com'),448            ]449        if subtype != 'no_content':450            ('content-shadow', 'Logrus'),451        msg_headers.append(('X-Random-Header', 'Corwin'))452        if subtype == 'text':453            payload = ''454            msg_headers.append(('Content-Type', 'text/plain'))455            m.set_payload('')456        elif subtype != 'no_content':457            payload = []458            msg_headers.append(('Content-Type', 'multipart/' + subtype))459        msg_headers.append(('X-Trump', 'Random'))460        m.set_payload(payload)461        for name, value in msg_headers:462            m[name] = value463        return m, msg_headers, payload464    def _check_disallowed_subtype_raises(self, m, method_name, subtype, method):465        with self.assertRaises(ValueError) as ar:466            getattr(m, method)()467        exc_text = str(ar.exception)468        self.assertIn(subtype, exc_text)469        self.assertIn(method_name, exc_text)470    def _check_make_multipart(self, m, msg_headers, payload):471        count = 0472        for name, value in msg_headers:473            if not name.lower().startswith('content-'):474                self.assertEqual(m[name], value)475                count += 1476        self.assertEqual(len(m), count+1) # +1 for new Content-Type477        part = next(m.iter_parts())478        count = 0479        for name, value in msg_headers:480            if name.lower().startswith('content-'):481                self.assertEqual(part[name], value)482                count += 1483        self.assertEqual(len(part), count)484        self.assertEqual(part.get_payload(), payload)485    def subtype_as_make(self, method, subtype, outcome):486        m, msg_headers, payload = self._make_subtype_test_message(subtype)487        make_method = 'make_' + method488        if outcome in ('', 'raises'):489            self._check_disallowed_subtype_raises(m, method, subtype, make_method)490            return491        getattr(m, make_method)()492        self.assertEqual(m.get_content_maintype(), 'multipart')493        self.assertEqual(m.get_content_subtype(), method)494        if subtype == 'no_content':495            self.assertEqual(len(m.get_payload()), 0)496            self.assertEqual(m.items(),497                             msg_headers + [('Content-Type',498                                             'multipart/'+method)])499        else:500            self.assertEqual(len(m.get_payload()), 1)501            self._check_make_multipart(m, msg_headers, payload)502    def subtype_as_make_with_boundary(self, method, subtype, outcome):503        # Doing all variation is a bit of overkill...504        m = self.message()505        if outcome in ('', 'raises'):506            m['Content-Type'] = 'multipart/' + subtype507            with self.assertRaises(ValueError) as cm:508                getattr(m, 'make_' + method)()509            return510        if subtype == 'plain':511            m['Content-Type'] = 'text/plain'512        elif subtype != 'no_content':513            m['Content-Type'] = 'multipart/' + subtype514        getattr(m, 'make_' + method)(boundary="abc")515        self.assertTrue(m.is_multipart())516        self.assertEqual(m.get_boundary(), 'abc')517    def test_policy_on_part_made_by_make_comes_from_message(self):518        for method in ('make_related', 'make_alternative', 'make_mixed'):519            m = self.message(policy=self.policy.clone(content_manager='foo'))520            m['Content-Type'] = 'text/plain'521            getattr(m, method)()522            self.assertEqual(m.get_payload(0).policy.content_manager, 'foo')523    class _TestSetContentManager:524        def set_content(self, msg, content, *args, **kw):525            msg['Content-Type'] = 'text/plain'526            msg.set_payload(content)527    def subtype_as_add(self, method, subtype, outcome):528        m, msg_headers, payload = self._make_subtype_test_message(subtype)529        cm = self._TestSetContentManager()530        add_method = 'add_attachment' if method=='mixed' else 'add_' + method531        if outcome == 'raises':532            self._check_disallowed_subtype_raises(m, method, subtype, add_method)533            return534        getattr(m, add_method)('test', content_manager=cm)535        self.assertEqual(m.get_content_maintype(), 'multipart')536        self.assertEqual(m.get_content_subtype(), method)537        if method == subtype or subtype == 'no_content':538            self.assertEqual(len(m.get_payload()), 1)539            for name, value in msg_headers:540                self.assertEqual(m[name], value)541            part = m.get_payload()[0]542        else:543            self.assertEqual(len(m.get_payload()), 2)544            self._check_make_multipart(m, msg_headers, payload)545            part = m.get_payload()[1]546        self.assertEqual(part.get_content_type(), 'text/plain')547        self.assertEqual(part.get_payload(), 'test')548        if method=='mixed':549            self.assertEqual(part['Content-Disposition'], 'attachment')550        elif method=='related':551            self.assertEqual(part['Content-Disposition'], 'inline')552        else:553            # Otherwise we don't guess.554            self.assertIsNone(part['Content-Disposition'])555    class _TestSetRaisingContentManager:556        def set_content(self, msg, content, *args, **kw):557            raise Exception('test')558    def test_default_content_manager_for_add_comes_from_policy(self):559        cm = self._TestSetRaisingContentManager()560        m = self.message(policy=self.policy.clone(content_manager=cm))561        for method in ('add_related', 'add_alternative', 'add_attachment'):562            with self.assertRaises(Exception) as ar:563                getattr(m, method)('')564            self.assertEqual(str(ar.exception), 'test')565    def message_as_clear(self, body_parts, attachments, parts, msg):566        m = self._str_msg(msg)567        m.clear()568        self.assertEqual(len(m), 0)569        self.assertEqual(list(m.items()), [])570        self.assertIsNone(m.get_payload())571        self.assertEqual(list(m.iter_parts()), [])572    def message_as_clear_content(self, body_parts, attachments, parts, msg):573        m = self._str_msg(msg)574        expected_headers = [h for h in m.keys()575                            if not h.lower().startswith('content-')]576        m.clear_content()577        self.assertEqual(list(m.keys()), expected_headers)578        self.assertIsNone(m.get_payload())579        self.assertEqual(list(m.iter_parts()), [])580    def test_is_attachment(self):581        m = self._make_message()582        self.assertFalse(m.is_attachment)583        m['Content-Disposition'] = 'inline'584        self.assertFalse(m.is_attachment)585        m.replace_header('Content-Disposition', 'attachment')586        self.assertTrue(m.is_attachment)587        m.replace_header('Content-Disposition', 'AtTachMent')588        self.assertTrue(m.is_attachment)589class TestEmailMessage(TestEmailMessageBase, TestEmailBase):590    message = EmailMessage591    def test_set_content_adds_MIME_Version(self):592        m = self._str_msg('')593        cm = self._TestContentManager()594        self.assertNotIn('MIME-Version', m)595        m.set_content(content_manager=cm)596        self.assertEqual(m['MIME-Version'], '1.0')597    class _MIME_Version_adding_CM:598        def set_content(self, msg, *args, **kw):599            msg['MIME-Version'] = '1.0'600    def test_set_content_does_not_duplicate_MIME_Version(self):601        m = self._str_msg('')602        cm = self._MIME_Version_adding_CM()603        self.assertNotIn('MIME-Version', m)604        m.set_content(content_manager=cm)605        self.assertEqual(m['MIME-Version'], '1.0')606class TestMIMEPart(TestEmailMessageBase, TestEmailBase):607    # Doing the full test run here may seem a bit redundant, since the two608    # classes are almost identical.  But what if they drift apart?  So we do609    # the full tests so that any future drift doesn't introduce bugs.610    message = MIMEPart611    def test_set_content_does_not_add_MIME_Version(self):612        m = self._str_msg('')613        cm = self._TestContentManager()614        self.assertNotIn('MIME-Version', m)615        m.set_content(content_manager=cm)616        self.assertNotIn('MIME-Version', m)617if __name__ == '__main__':...basecontenteditor.py
Source:basecontenteditor.py  
1from zoundry.appframework.global_services import getLoggerService2from zoundry.appframework.ui.events.editcontrolevents import IZEditControlEvents3from zoundry.appframework.ui.events.toolbarevents import ZEVT_TOOLBAR_RESIZE4from zoundry.appframework.ui.util.colorutil import getDefaultDialogBackgroundColor5from zoundry.appframework.ui.widgets.controls.advanced.editcontrol import IZRichTextEditControl6from zoundry.appframework.ui.widgets.controls.common.acceleratortable import ZAcceleratorEntry7from zoundry.appframework.ui.widgets.controls.common.acceleratortable import ZAcceleratorTable8from zoundry.appframework.ui.widgets.controls.common.menu.menu import ZMenu9from zoundry.appframework.ui.widgets.controls.common.menu.menumodel import ZModelBasedMenuContentProvider10from zoundry.appframework.ui.widgets.controls.common.menu.menumodel import ZModelBasedMenuEventHandler11from zoundry.appframework.ui.widgets.controls.common.toolbar.toolbar import ZToolBar12from zoundry.appframework.ui.widgets.controls.common.toolbar.toolbarmodel import ZModelBasedToolBarContentProvider13from zoundry.appframework.ui.widgets.controls.common.toolbar.toolbarmodel import ZModelBasedToolBarEventHandler14from zoundry.appframework.ui.widgets.dialogs.standarddialogs import ZShowNotYetImplementedMessage15from zoundry.base.exceptions import ZAbstractMethodCalledException16from zoundry.blogapp.constants import IZBlogAppAcceleratorIds17from zoundry.blogapp.services.datastore.documentimpl import ZXhtmlContent18from zoundry.blogapp.ui.actions.blogeditor.blogeditoractions import ZBlogPostEditorToolBarActionContext19from zoundry.blogapp.ui.actions.blogeditor.blogeditoractions import ZBlogPostInsertImageAction20from zoundry.blogapp.ui.actions.blogeditor.blogeditoractions import ZBlogPostInsertImgTagAction21from zoundry.blogapp.ui.actions.blogeditor.blogeditoractions import ZBlogPostInsertLinkAction22from zoundry.blogapp.ui.actions.blogeditor.blogeditoractions import ZBlogPostSpellCheckAction23from zoundry.blogapp.ui.actions.blogeditor.blogeditoractions import ZBlogRichTextFormatAction24from zoundry.blogapp.ui.actions.blogeditor.blogeditoractions import ZFocusOnTagwordsAction25from zoundry.blogapp.ui.actions.blogeditor.blogeditoractions import ZFocusOnTitleAction26from zoundry.blogapp.ui.editors.blogeditorctrls.blogposteditcontrol import IZBlogPostEditControl27from zoundry.blogapp.ui.editors.blogeditorctrls.metadata import ZBlogPostMetaDataWidget28from zoundry.blogapp.ui.menus.blogeditor.blogcontenteditorcontextmenumodel import ZBlogContentEditorContextMenuModel29from zoundry.blogapp.ui.menus.blogeditor.blogeditortoolbarmodel import ZBlogContentEditorToolbarModel30import wx3132# ------------------------------------------------------------------------------33# Interface that blog content editor controls must implement.34# ------------------------------------------------------------------------------35class IZBlogContentEditorControl:3637    def refreshUI(self):38        u"""refreshUI() -> void39        Updates the UI as well editor content based on the model data40        """ #$NON-NLS-1$41    # end refreshUI()4243    def updateModel(self):44        u"""updateModel() -> void45        Updates the model data (title, xhtml document etc) from the UI controls.46        """ #$NON-NLS-1$47    # end updateModel()4849    def modelSaved(self):50        u"""modelSaved() -> void51        Invoked by the editor framework when the model data has been persisted.52        """ #$NON-NLS-1$53    # end modelSaved()5455# end IZBlogContentEditorControl565758# ------------------------------------------------------------------------------59# Implements the accelerator table for the blog post content editor.60# ------------------------------------------------------------------------------61class ZBlogPostContentEditorAcceleratorTable(ZAcceleratorTable):6263    def __init__(self, context):64        self.context = context65        ZAcceleratorTable.__init__(self, IZBlogAppAcceleratorIds.ZID_BLOG_POST_EDITOR_CONTENT_ACCEL)66    # end __init__()6768    def _createActionContext(self):69        return self.context70    # end _createActionContext()7172    def _loadAdditionalEntries(self):73        return [74            # Bold, Italic, Underline, Strike75            ZAcceleratorEntry(wx.ACCEL_CTRL, ord(u'B'), ZBlogRichTextFormatAction(IZRichTextEditControl.ZCAPABILITY_BOLD)), #$NON-NLS-1$76            ZAcceleratorEntry(wx.ACCEL_CTRL, ord(u'I'), ZBlogRichTextFormatAction(IZRichTextEditControl.ZCAPABILITY_ITALIC)), #$NON-NLS-1$77            ZAcceleratorEntry(wx.ACCEL_CTRL, ord(u'U'), ZBlogRichTextFormatAction(IZRichTextEditControl.ZCAPABILITY_UNDERLINE)), #$NON-NLS-1$78            # create link79            ZAcceleratorEntry(wx.ACCEL_CTRL, ord(u'L'), ZBlogPostInsertLinkAction()), #$NON-NLS-1$80            ZAcceleratorEntry(wx.ACCEL_CTRL, ord(u'K'), ZBlogPostInsertLinkAction()), #$NON-NLS-1$81            # insert image82            ZAcceleratorEntry(wx.ACCEL_CTRL, ord(u'M'), ZBlogPostInsertImageAction()), #$NON-NLS-1$,83            ZAcceleratorEntry(wx.ACCEL_CTRL + wx.ACCEL_SHIFT, ord(u'M'), ZBlogPostInsertImgTagAction()), #$NON-NLS-1$,84            # spell check85            ZAcceleratorEntry(wx.ACCEL_NORMAL, wx.WXK_F7, ZBlogPostSpellCheckAction()),86            # Go to Title87            ZAcceleratorEntry(wx.ACCEL_ALT, ord(u'T'), ZFocusOnTitleAction()), #$NON-NLS-1$88            # Go to Tagwords89            ZAcceleratorEntry(wx.ACCEL_SHIFT, wx.WXK_TAB, ZFocusOnTagwordsAction()), #$NON-NLS-1$90        ]91    # end _loadAdditionalEntries()9293# end ZBlogPostContentEditorAcceleratorTable949596# ------------------------------------------------------------------------------97# This is the base class of the  blog post content area editor.98# Concrete implementations are WsyiWyg and XhtmlText content editors99# ------------------------------------------------------------------------------100class ZBlogPostContentEditorBase(wx.Panel, IZBlogContentEditorControl):101102    def __init__(self, parentWindow, zblogPostEditor, zblogPostEditorModel):103        self.zblogPostEditor = zblogPostEditor104        self.zblogPostEditorModel = zblogPostEditorModel105        self.editor = None106        self.metaDataWidget = None107        self.contentEditCtrl = None108        # dirty: internally keep track if the editor (mshtml/scintilla) content has been modified.109        self.contentModified = False110        wx.Panel.__init__(self, parentWindow, wx.ID_ANY)111        self._createWidgets()112        self._layoutWidgets()113        self._bindWidgetEvents()114        if self._getContentEditControl():115            self._bindContentEditCtrlEvents()116    # end __init__()117118    def focusOnContent(self):119        self.contentEditCtrl.SetFocus()120    # end focusOnContent()121122    def getMetaDataWidget(self):123        return self.metaDataWidget124    # end getMetaDataWidget()125126    def _getModel(self):127        return self.zblogPostEditorModel128    # end _getModel129130    def _getContentEditControl(self):131        return self.contentEditCtrl132    # end _getContentEditControl133134    def _createWidgets(self):135        self.SetBackgroundColour(wx.Colour(255, 255, 255))136        self.metaDataWidget = ZBlogPostMetaDataWidget(self, self._getModel().getMetaDataModel() )137        self.metaDataWidget.SetBackgroundColour(getDefaultDialogBackgroundColor())138        self.contentEditCtrl = self._createContentEditCtrl(self)139        # Note: toolbar must be created only after the contentEditCtrl has been created (so that edit control capabilities can be determined)140        self.toolBar = self._createToolBar()141        self.tbStaticLine = wx.StaticLine(self, wx.ID_ANY)142143        self.acceleratorTable = ZBlogPostContentEditorAcceleratorTable(ZBlogPostEditorToolBarActionContext(self))144        self.contentEditCtrl.SetAcceleratorTable(self.acceleratorTable)145    # end _createWidgets()146147    def _createContentEditCtrl(self, parent):148        # sublcasses must create concrete IZBlogPostEditControl impl.149        raise ZAbstractMethodCalledException(self.__class__.__name__, u"_createContentEditCtrl") #$NON-NLS-1$150    # end _createContentEditCtrl()151152    def _layoutWidgets(self):153        self.sizer = wx.BoxSizer(wx.VERTICAL)154        self.sizer.Add(self.metaDataWidget, 0, wx.EXPAND)155        self.sizer.Add(self.toolBar, 0, wx.EXPAND)156        self.sizer.Add(self.tbStaticLine, 0, wx.EXPAND)157        self.sizer.Add(self.contentEditCtrl, 1, wx.EXPAND)158        self.SetSizer(self.sizer)159        self.SetAutoLayout(True)160        self.Layout()161    # end _layoutWidgets()162163    def _bindWidgetEvents(self):164        self.Bind(ZEVT_TOOLBAR_RESIZE, self.onToolBarResize, self.toolBar)165        wx.EVT_NAVIGATION_KEY(self.metaDataWidget, self.onMetaDataKeyboardNavigation)166        self.acceleratorTable.bindTo(self)167    # end _bindWidgetEvents()168169    def _bindContentEditCtrlEvents(self):170        self.Bind(IZEditControlEvents.ZEVT_UPDATE_UI, self.onUpdateUI, self._getContentEditControl())171        self.Bind(IZEditControlEvents.ZEVT_SELECTION_CHANGE, self.onSelectionChange, self._getContentEditControl())172        self.Bind(IZEditControlEvents.ZEVT_CONTEXT_MENU, self.onContextMenu, self._getContentEditControl())173        self.Bind(IZEditControlEvents.ZEVT_CONTENT_MODIFIED, self.onContentModified, self._getContentEditControl())174    # end _bindContentEditCtrlEvents()175176    def _createToolBar(self):177        self.toolBarModel = self._createToolBarModel()178        self.toolBarContext = ZBlogPostEditorToolBarActionContext(self)179        contentProvider = ZModelBasedToolBarContentProvider(self.toolBarModel, self.toolBarContext)180        eventHandler = ZModelBasedToolBarEventHandler(self.toolBarModel, self.toolBarContext)181        return ZToolBar(contentProvider, eventHandler, self)182    # end _createToolBar()183184    def _createToolBarModel(self):185        toolbarModel = ZBlogContentEditorToolbarModel()186        return toolbarModel187    # end _createToolBarModel()188189    def _isContentModified(self):190        return self.contentModified191    #end _isContentModified()192193    def _setContentModified(self, modified):194        self.contentModified = modified195196    def onUpdateUI(self, event): #@UnusedVariable197        self.zblogPostEditor._fireUpdateMenu()        198        self.toolBar.refresh()199        self._updateCaretPostionUI()200    # end onUIUpdate()201    202    def _updateCaretPostionUI(self):203        (row, col) = self._getContentEditControl().getCaretPosition()204        text = u"" #$NON-NLS-1$205        if row != -1 and col != -1:206            text = u"%d : %d" % (row, col) #$NON-NLS-1$207        self.zblogPostEditor.statusBarModel.setPaneText(u"rowcol", text) #$NON-NLS-1$208        self.zblogPostEditor._fireStatusBarChangedEvent()209    # end _updateCaretPostionUI()210211    def onContentModified(self, event): #@UnusedVariable212        # This is the event handler for mshtml/stc content modified/dirty indicator.213        # Notify the blog post editor just one time214        if not self._isContentModified():215            self.zblogPostEditor.setDirty(True)216        self._setContentModified(True)217    # end onContentModified()218219    def onSelectionChange(self, event): #@UnusedVariable220        pass221    # end onSelectionChange()222223    def onContextMenu(self, event):224        linkCtx = self.getLinkContext()225        imageCtx = self.getImageContext()226        tableCtx = self.getTableContext()227        removeExtMarker = False228        if self.hasCapability(IZBlogPostEditControl.ZCAPABILITY_EXTENDED_ENTRY_MARKER) \229            and self._getContentEditControl().canRemoveExtendedEntryMarker():230            removeExtMarker = True231232        menuModel = ZBlogContentEditorContextMenuModel()233        menuModel.initialize(linkCtx, imageCtx, tableCtx, removeExtMarker)234235        menuContext = self.zblogPostEditor.getMenuActionContext()236        contentProvider = ZModelBasedMenuContentProvider(menuModel, menuContext)237        eventHandler = ZModelBasedMenuEventHandler(menuModel, menuContext)238        menu = ZMenu(event.getParentWindow(), menuModel.getRootNode(), contentProvider, eventHandler)239        try:240            event.getParentWindow().PopupMenu(menu, event.getXYPoint())241        except Exception, e:242            getLoggerService().exception(e)243        menu.Destroy()244    # end onContextMenu()245246    def refreshUI(self):247        document = self.zblogPostEditorModel.getDocument()248        # refresh title, tags, blog data etc.249        self.metaDataWidget.refreshUI()250        # get xhtml content and set it in the edit control.251        if document.getContent() is not None:252            xhtmlDoc = document.getContent().getXhtmlDocument()253            self._getContentEditControl().setXhtmlDocument(xhtmlDoc)254    # end refreshUI()255256    def modelSaved(self):257        # model was saved to data store.258        # Clear dirty flag so that onContentModified() handler can set the flag in the model259        self._setContentModified(False)260        # Clear flags (such as 'content modified') in the content editor (eg. ZMSHTMLBlogPostEditControl via ZBlogPostWysiwygContentEditor)261        self._getContentEditControl().clearState()262        # Refresh widget UI.263        self.metaDataWidget.refreshUI()264    # end modelSaved()265266    def updateModel(self):267        document = self.zblogPostEditorModel.getDocument()268        # Flush title, tags, blog meta data etc. from UI controls to zmetadata model.269        self.metaDataWidget.updateModel()270        #  Next copy title, tags etc. from meta data model to the document.271        self.zblogPostEditorModel.getMetaDataModel().updateDocument(document)272273        # Finally, get the Xhtml content from the editor and set it in the document274        xhtmlDoc = self._getContentEditControl().getXhtmlDocument()275        xhtmlContent = ZXhtmlContent()276        xhtmlContent.setXhtmlDocument(xhtmlDoc)277        document.setContent(xhtmlContent)278    # end updateModel()279280    def onTool(self, ctx): #@UnusedVariable281        ZShowNotYetImplementedMessage(self)282    # end onTool()283284    def onToolBarResize(self, event):285        self.Layout()286        event.Skip()287    # end onToolBarResize()288289    # This is a bit hack-y - when tabbing from the tagwords text control, in290    # the forward direction, the focus does not get properly set to the291    # content editor.  I'm not really sure where it goes, actually.  This292    # method will listen for keyboard nav events in the meta data widget and293    # re-route a forward direction Tab event so that it sets the focus294    # explicitly on the content editor.295    def onMetaDataKeyboardNavigation(self, event):296        focusWidget = self.metaDataWidget.FindFocus()297        if event.GetDirection() and event.IsFromTab() and focusWidget == self.metaDataWidget.tagwordsText:298            self.focusOnContent()299        else:300            event.Skip()301    # end onMetaDataKeyboardNavigation()302303    def hasCapability(self, capabilityId):304        return self._getContentEditControl().getCapabilities().hasCapability(capabilityId)305    # end hasCapability()306307    def canCut(self):308        return self._getContentEditControl().canCut()309    # end canCut()310311    def cut(self):312        self._getContentEditControl().cut()313    # end cut()314315    def canCopy(self):316        return self._getContentEditControl().canCopy()317    # end canCopy()318319    def copy(self):320        self._getContentEditControl().copy()321    # end copy()322323    def canPaste(self, xhtmlFormat):324        if xhtmlFormat:325            return self._getContentEditControl().canPasteXhtml()326        else:327            return self._getContentEditControl().canPaste()328    # end canPaste()329330    def paste(self, xhtmlFormat):331        if xhtmlFormat:332            self._getContentEditControl().pasteXhtml()333        else:334            self._getContentEditControl().paste()335    # end paste()336337    def canInsertXhtml(self):338        return self._getContentEditControl().canInsertXhtml()339    # end canInsertXhtml340341    def insertXhtml(self, xhtmlString): #@UnusedVariable342        self._getContentEditControl().insertXhtml(xhtmlString)343    # end insertXhtml344345    def canSelectAll(self):346        return self._getContentEditControl().canSelectAll()347    # end canSelectAll()348349    def selectAll(self):350        self._getContentEditControl().selectAll()351    # end selectAll()352353    def canUndo(self):354        return self._getContentEditControl().canUndo()355    # end canUndo()356357    def undo(self):358        self._getContentEditControl().undo()359    # end undo()360361    def canRedo(self):362        return self._getContentEditControl().canRedo()363    # end canRedo()364365    def redo(self):366        self._getContentEditControl().redo()367    # end redo()368369    # ---------------------------------------------370    # RichTextEdit formatting commands. Eg. Bold, Italic.371    # ---------------------------------------------372373    def isFormattingEnabled(self, capabilityId):374        return self._getContentEditControl().isFormattingEnabled(capabilityId)375    # end isFormattingEnabled()376377    def getFormattingState(self, capabilityId):378        return self._getContentEditControl().getFormattingState(capabilityId)379    # end getFormattingState()380381    def applyFormatting(self, capabilityId, customData):382        self._getContentEditControl().applyFormatting(capabilityId, customData)383    # end applyFormatting()384385    def getLinkContext(self):386        u"""getLinkContext()  -> IZXHTMLEditControlLinkContext387        Returns link creation and edit context if available or None otherwise.""" #$NON-NLS-1$388        return self._getContentEditControl().getLinkContext()389    # end getLinkContext390391    def getImageContext(self):392        u"""getImageContext()  -> IZXHTMLEditControlImageContext393        Returns image creation and edit context if available or None otherwise.""" #$NON-NLS-1$394        return self._getContentEditControl().getImageContext()395    # end getImageContext396397    def getTableContext(self):398        u"""getTableContext()  -> IZXHTMLEditControlTableContext399        Returns table insertion and edit context if available or None otherwise.""" #$NON-NLS-1$400        return self._getContentEditControl().getTableContext()401    # end getTableContext402403    def getCurrentSelection(self):404        u"""getCurrentSelection() -> IZXHTMLEditControlSelection405        """ #$NON-NLS-1$406        return self._getContentEditControl().getCurrentSelection()407    # end getCurrentSelection()408409    # ---------------------------------------------410    # Extended entry411    # ---------------------------------------------412413    def insertExtendedEntryMarker(self):414        self._getContentEditControl().insertExtendedEntryMarker()415    # end insertExtendedEntryMarker()416417    def removeExtendedEntryMarker(self):418        self._getContentEditControl().removeExtendedEntryMarker()419    # removeExtendedEntryMarker()420421    # ---------------------------------------------422    # Spellcheck423    # ---------------------------------------------424    def createSpellCheckContext(self):425        u"""createSpellCheckContext() -> IZEditControlSpellCheckContext426        Returns IZEditControlSpellCheckContext if spellcheck is supported or None otherwise.427        """ #$NON-NLS-1$428        return self._getContentEditControl().createSpellCheckContext()429    # end createSpellCheckContext()430431    # ---------------------------------------------432    # Find and Find/Replace433    # ---------------------------------------------434    def createFindReplaceContext(self):435        u"""createFindReplaceContext() -> IZEditControlFindReplaceTextContext436        Returns IZEditControlFindReplaceTextContext if find/replace is supported or None otherwise.437        """ #$NON-NLS-1$438        return self._getContentEditControl().createFindReplaceContext()439    # end createFindReplaceContext()440    441    # ---------------------------------------------442    # Validate and Tidy443    # ---------------------------------------------444    def schemaValidate(self):445        self._getContentEditControl().schemaValidate()446    # end schemaValidate447    448    def clearValidation(self):449        self._getContentEditControl().clearValidation()450    # end clearValidation    451    452    def runTidy(self):453        self._getContentEditControl().runTidy()454    # end runTidy      455    456
...LambdaTest’s Playwright tutorial will give you a broader idea about the Playwright automation framework, its unique features, and use cases with examples to exceed your understanding of Playwright testing. This tutorial will give A to Z guidance, from installing the Playwright framework to some best practices and advanced concepts.
Get 100 minutes of automation test minutes FREE!!
