Initial import to Tizen
[profile/ivi/python-twisted.git] / twisted / web / test / test_template.py
1
2 # Copyright (c) Twisted Matrix Laboratories.
3 # See LICENSE for details.
4
5
6 """
7 Tests for L{twisted.web.template}
8 """
9
10 from cStringIO import StringIO
11
12 from zope.interface.verify import verifyObject
13
14 from twisted.internet.defer import succeed, gatherResults
15 from twisted.python.filepath import FilePath
16 from twisted.trial.unittest import TestCase
17 from twisted.web.template import (
18     Element, TagLoader, renderer, tags, XMLFile, XMLString)
19 from twisted.web.iweb import ITemplateLoader
20
21 from twisted.web.error import (FlattenerError, MissingTemplateLoader,
22     MissingRenderMethod)
23
24 from twisted.web.template import renderElement
25 from twisted.web._element import UnexposedMethodError
26 from twisted.web.test._util import FlattenTestCase
27 from twisted.web.test.test_web import DummyRequest
28 from twisted.web.server import NOT_DONE_YET
29
30 class TagFactoryTests(TestCase):
31     """
32     Tests for L{_TagFactory} through the publicly-exposed L{tags} object.
33     """
34     def test_lookupTag(self):
35         """
36         HTML tags can be retrieved through C{tags}.
37         """
38         tag = tags.a
39         self.assertEqual(tag.tagName, "a")
40
41
42     def test_lookupHTML5Tag(self):
43         """
44         Twisted supports the latest and greatest HTML tags from the HTML5
45         specification.
46         """
47         tag = tags.video
48         self.assertEqual(tag.tagName, "video")
49
50
51     def test_lookupTransparentTag(self):
52         """
53         To support transparent inclusion in templates, there is a special tag,
54         the transparent tag, which has no name of its own but is accessed
55         through the "transparent" attribute.
56         """
57         tag = tags.transparent
58         self.assertEqual(tag.tagName, "")
59
60
61     def test_lookupInvalidTag(self):
62         """
63         Invalid tags which are not part of HTML cause AttributeErrors when
64         accessed through C{tags}.
65         """
66         self.assertRaises(AttributeError, getattr, tags, "invalid")
67
68
69     def test_lookupXMP(self):
70         """
71         As a special case, the <xmp> tag is simply not available through
72         C{tags} or any other part of the templating machinery.
73         """
74         self.assertRaises(AttributeError, getattr, tags, "xmp")
75
76
77
78 class ElementTests(TestCase):
79     """
80     Tests for the awesome new L{Element} class.
81     """
82     def test_missingTemplateLoader(self):
83         """
84         L{Element.render} raises L{MissingTemplateLoader} if the C{loader}
85         attribute is C{None}.
86         """
87         element = Element()
88         err = self.assertRaises(MissingTemplateLoader, element.render, None)
89         self.assertIdentical(err.element, element)
90
91
92     def test_missingTemplateLoaderRepr(self):
93         """
94         A L{MissingTemplateLoader} instance can be repr()'d without error.
95         """
96         class PrettyReprElement(Element):
97             def __repr__(self):
98                 return 'Pretty Repr Element'
99         self.assertIn('Pretty Repr Element',
100                       repr(MissingTemplateLoader(PrettyReprElement())))
101
102
103     def test_missingRendererMethod(self):
104         """
105         When called with the name which is not associated with a render method,
106         L{Element.lookupRenderMethod} raises L{MissingRenderMethod}.
107         """
108         element = Element()
109         err = self.assertRaises(
110             MissingRenderMethod, element.lookupRenderMethod, "foo")
111         self.assertIdentical(err.element, element)
112         self.assertEqual(err.renderName, "foo")
113
114
115     def test_missingRenderMethodRepr(self):
116         """
117         A L{MissingRenderMethod} instance can be repr()'d without error.
118         """
119         class PrettyReprElement(Element):
120             def __repr__(self):
121                 return 'Pretty Repr Element'
122         s = repr(MissingRenderMethod(PrettyReprElement(),
123                                      'expectedMethod'))
124         self.assertIn('Pretty Repr Element', s)
125         self.assertIn('expectedMethod', s)
126
127
128     def test_definedRenderer(self):
129         """
130         When called with the name of a defined render method,
131         L{Element.lookupRenderMethod} returns that render method.
132         """
133         class ElementWithRenderMethod(Element):
134             @renderer
135             def foo(self, request, tag):
136                 return "bar"
137         foo = ElementWithRenderMethod().lookupRenderMethod("foo")
138         self.assertEqual(foo(None, None), "bar")
139
140
141     def test_render(self):
142         """
143         L{Element.render} loads a document from the C{loader} attribute and
144         returns it.
145         """
146         class TemplateLoader(object):
147             def load(self):
148                 return "result"
149
150         class StubElement(Element):
151             loader = TemplateLoader()
152
153         element = StubElement()
154         self.assertEqual(element.render(None), "result")
155
156
157     def test_misuseRenderer(self):
158         """
159         If the L{renderer} decorator  is called without any arguments, it will
160         raise a comprehensible exception.
161         """
162         te = self.assertRaises(TypeError, renderer)
163         self.assertEqual(str(te),
164                          "expose() takes at least 1 argument (0 given)")
165
166
167     def test_renderGetDirectlyError(self):
168         """
169         Called directly, without a default, L{renderer.get} raises
170         L{UnexposedMethodError} when it cannot find a renderer.
171         """
172         self.assertRaises(UnexposedMethodError, renderer.get, None,
173                           "notARenderer")
174
175
176
177 class XMLFileReprTests(TestCase):
178     """
179     Tests for L{twisted.web.template.XMLFile}'s C{__repr__}.
180     """
181     def test_filePath(self):
182         """
183         An L{XMLFile} with a L{FilePath} returns a useful repr().
184         """
185         path = FilePath("/tmp/fake.xml")
186         self.assertEqual('<XMLFile of %r>' % (path,), repr(XMLFile(path)))
187
188
189     def test_filename(self):
190         """
191         An L{XMLFile} with a filename returns a useful repr().
192         """
193         fname = "/tmp/fake.xml"
194         self.assertEqual('<XMLFile of %r>' % (fname,), repr(XMLFile(fname)))
195
196
197     def test_file(self):
198         """
199         An L{XMLFile} with a file object returns a useful repr().
200         """
201         fobj = StringIO("not xml")
202         self.assertEqual('<XMLFile of %r>' % (fobj,), repr(XMLFile(fobj)))
203
204
205
206 class XMLLoaderTestsMixin(object):
207     """
208     @ivar templateString: Simple template to use to excercise the loaders.
209
210     @ivar deprecatedUse: C{True} if this use of L{XMLFile} is deprecated and
211         should emit a C{DeprecationWarning}.
212     """
213
214     loaderFactory = None
215     templateString = '<p>Hello, world.</p>'
216     def test_load(self):
217         """
218         Verify that the loader returns a tag with the correct children.
219         """
220         loader = self.loaderFactory()
221         tag, = loader.load()
222
223         warnings = self.flushWarnings(offendingFunctions=[self.loaderFactory])
224         if self.deprecatedUse:
225             self.assertEqual(len(warnings), 1)
226             self.assertEqual(warnings[0]['category'], DeprecationWarning)
227             self.assertEqual(
228                 warnings[0]['message'],
229                 "Passing filenames or file objects to XMLFile is "
230                 "deprecated since Twisted 12.1.  Pass a FilePath instead.")
231         else:
232             self.assertEqual(len(warnings), 0)
233
234         self.assertEqual(tag.tagName, 'p')
235         self.assertEqual(tag.children, [u'Hello, world.'])
236
237
238     def test_loadTwice(self):
239         """
240         If {load()} can be called on a loader twice the result should be the
241         same.
242         """
243         loader = self.loaderFactory()
244         tags1 = loader.load()
245         tags2 = loader.load()
246         self.assertEqual(tags1, tags2)
247
248
249
250 class XMLStringLoaderTests(TestCase, XMLLoaderTestsMixin):
251     """
252     Tests for L{twisted.web.template.XMLString}
253     """
254     deprecatedUse = False
255     def loaderFactory(self):
256         """
257         @return: an L{XMLString} constructed with C{self.templateString}.
258         """
259         return XMLString(self.templateString)
260
261
262
263 class XMLFileWithFilePathTests(TestCase, XMLLoaderTestsMixin):
264     """
265     Tests for L{twisted.web.template.XMLFile}'s L{FilePath} support.
266     """
267     deprecatedUse = False
268     def loaderFactory(self):
269         """
270         @return: an L{XMLString} constructed with a L{FilePath} pointing to a
271             file that contains C{self.templateString}.
272         """
273         fp = FilePath(self.mktemp())
274         fp.setContent(self.templateString)
275         return XMLFile(fp)
276
277
278
279 class XMLFileWithFileTests(TestCase, XMLLoaderTestsMixin):
280     """
281     Tests for L{twisted.web.template.XMLFile}'s deprecated file object support.
282     """
283     deprecatedUse = True
284     def loaderFactory(self):
285         """
286         @return: an L{XMLString} constructed with a file object that contains
287             C{self.templateString}.
288         """
289         return XMLFile(StringIO(self.templateString))
290
291
292
293 class XMLFileWithFilenameTests(TestCase, XMLLoaderTestsMixin):
294     """
295     Tests for L{twisted.web.template.XMLFile}'s deprecated filename support.
296     """
297     deprecatedUse = True
298     def loaderFactory(self):
299         """
300         @return: an L{XMLString} constructed with a filename that points to a
301             file containing C{self.templateString}.
302         """
303         fp = FilePath(self.mktemp())
304         fp.setContent(self.templateString)
305         return XMLFile(fp.path)
306
307
308
309 class FlattenIntegrationTests(FlattenTestCase):
310     """
311     Tests for integration between L{Element} and
312     L{twisted.web._flatten.flatten}.
313     """
314
315     def test_roundTrip(self):
316         """
317         Given a series of parsable XML strings, verify that
318         L{twisted.web._flatten.flatten} will flatten the L{Element} back to the
319         input when sent on a round trip.
320         """
321         fragments = [
322             "<p>Hello, world.</p>",
323             "<p><!-- hello, world --></p>",
324             "<p><![CDATA[Hello, world.]]></p>",
325             '<test1 xmlns:test2="urn:test2">'
326                 '<test2:test3></test2:test3></test1>',
327             '<test1 xmlns="urn:test2"><test3></test3></test1>',
328             '<p>\xe2\x98\x83</p>',
329         ]
330         deferreds = [
331             self.assertFlattensTo(Element(loader=XMLString(xml)), xml)
332             for xml in fragments]
333         return gatherResults(deferreds)
334
335
336     def test_entityConversion(self):
337         """
338         When flattening an HTML entity, it should flatten out to the utf-8
339         representation if possible.
340         """
341         element = Element(loader=XMLString('<p>&#9731;</p>'))
342         return self.assertFlattensTo(element, '<p>\xe2\x98\x83</p>')
343
344
345     def test_missingTemplateLoader(self):
346         """
347         Rendering a Element without a loader attribute raises the appropriate
348         exception.
349         """
350         return self.assertFlatteningRaises(Element(), MissingTemplateLoader)
351
352
353     def test_missingRenderMethod(self):
354         """
355         Flattening an L{Element} with a C{loader} which has a tag with a render
356         directive fails with L{FlattenerError} if there is no available render
357         method to satisfy that directive.
358         """
359         element = Element(loader=XMLString("""
360         <p xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1"
361           t:render="unknownMethod" />
362         """))
363         return self.assertFlatteningRaises(element, MissingRenderMethod)
364
365
366     def test_transparentRendering(self):
367         """
368         A C{transparent} element should be eliminated from the DOM and rendered as
369         only its children.
370         """
371         element = Element(loader=XMLString(
372             '<t:transparent '
373             'xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1">'
374             'Hello, world.'
375             '</t:transparent>'
376         ))
377         return self.assertFlattensTo(element, "Hello, world.")
378
379
380     def test_attrRendering(self):
381         """
382         An Element with an attr tag renders the vaule of its attr tag as an
383         attribute of its containing tag.
384         """
385         element = Element(loader=XMLString(
386             '<a xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1">'
387             '<t:attr name="href">http://example.com</t:attr>'
388             'Hello, world.'
389             '</a>'
390         ))
391         return self.assertFlattensTo(element,
392             '<a href="http://example.com">Hello, world.</a>')
393
394
395     def test_errorToplevelAttr(self):
396         """
397         A template with a toplevel C{attr} tag will not load; it will raise
398         L{AssertionError} if you try.
399         """
400         self.assertRaises(
401             AssertionError,
402             XMLString,
403             """<t:attr
404             xmlns:t='http://twistedmatrix.com/ns/twisted.web.template/0.1'
405             name='something'
406             >hello</t:attr>
407             """)
408
409
410     def test_errorUnnamedAttr(self):
411         """
412         A template with an C{attr} tag with no C{name} attribute will not load;
413         it will raise L{AssertionError} if you try.
414         """
415         self.assertRaises(
416             AssertionError,
417             XMLString,
418             """<html><t:attr
419             xmlns:t='http://twistedmatrix.com/ns/twisted.web.template/0.1'
420             >hello</t:attr></html>""")
421
422
423     def test_lenientPrefixBehavior(self):
424         """
425         If the parser sees a prefix it doesn't recognize on an attribute, it
426         will pass it on through to serialization.
427         """
428         theInput = (
429             '<hello:world hello:sample="testing" '
430             'xmlns:hello="http://made-up.example.com/ns/not-real">'
431             'This is a made-up tag.</hello:world>')
432         element = Element(loader=XMLString(theInput))
433         self.assertFlattensTo(element, theInput)
434
435
436     def test_deferredRendering(self):
437         """
438         An Element with a render method which returns a Deferred will render
439         correctly.
440         """
441         class RenderfulElement(Element):
442             @renderer
443             def renderMethod(self, request, tag):
444                 return succeed("Hello, world.")
445         element = RenderfulElement(loader=XMLString("""
446         <p xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1"
447           t:render="renderMethod">
448             Goodbye, world.
449         </p>
450         """))
451         return self.assertFlattensTo(element, "Hello, world.")
452
453
454     def test_loaderClassAttribute(self):
455         """
456         If there is a non-None loader attribute on the class of an Element
457         instance but none on the instance itself, the class attribute is used.
458         """
459         class SubElement(Element):
460             loader = XMLString("<p>Hello, world.</p>")
461         return self.assertFlattensTo(SubElement(), "<p>Hello, world.</p>")
462
463
464     def test_directiveRendering(self):
465         """
466         An Element with a valid render directive has that directive invoked and
467         the result added to the output.
468         """
469         renders = []
470         class RenderfulElement(Element):
471             @renderer
472             def renderMethod(self, request, tag):
473                 renders.append((self, request))
474                 return tag("Hello, world.")
475         element = RenderfulElement(loader=XMLString("""
476         <p xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1"
477           t:render="renderMethod" />
478         """))
479         return self.assertFlattensTo(element, "<p>Hello, world.</p>")
480
481
482     def test_directiveRenderingOmittingTag(self):
483         """
484         An Element with a render method which omits the containing tag
485         successfully removes that tag from the output.
486         """
487         class RenderfulElement(Element):
488             @renderer
489             def renderMethod(self, request, tag):
490                 return "Hello, world."
491         element = RenderfulElement(loader=XMLString("""
492         <p xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1"
493           t:render="renderMethod">
494             Goodbye, world.
495         </p>
496         """))
497         return self.assertFlattensTo(element, "Hello, world.")
498
499
500     def test_elementContainingStaticElement(self):
501         """
502         An Element which is returned by the render method of another Element is
503         rendered properly.
504         """
505         class RenderfulElement(Element):
506             @renderer
507             def renderMethod(self, request, tag):
508                 return tag(Element(
509                     loader=XMLString("<em>Hello, world.</em>")))
510         element = RenderfulElement(loader=XMLString("""
511         <p xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1"
512           t:render="renderMethod" />
513         """))
514         return self.assertFlattensTo(element, "<p><em>Hello, world.</em></p>")
515
516
517     def test_elementUsingSlots(self):
518         """
519         An Element which is returned by the render method of another Element is
520         rendered properly.
521         """
522         class RenderfulElement(Element):
523             @renderer
524             def renderMethod(self, request, tag):
525                 return tag.fillSlots(test2='world.')
526         element = RenderfulElement(loader=XMLString(
527             '<p xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1"'
528             ' t:render="renderMethod">'
529             '<t:slot name="test1" default="Hello, " />'
530             '<t:slot name="test2" />'
531             '</p>'
532         ))
533         return self.assertFlattensTo(element, "<p>Hello, world.</p>")
534
535
536     def test_elementContainingDynamicElement(self):
537         """
538         Directives in the document factory of a Element returned from a render
539         method of another Element are satisfied from the correct object: the
540         "inner" Element.
541         """
542         class OuterElement(Element):
543             @renderer
544             def outerMethod(self, request, tag):
545                 return tag(InnerElement(loader=XMLString("""
546                 <t:ignored
547                   xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1"
548                   t:render="innerMethod" />
549                 """)))
550         class InnerElement(Element):
551             @renderer
552             def innerMethod(self, request, tag):
553                 return "Hello, world."
554         element = OuterElement(loader=XMLString("""
555         <p xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1"
556           t:render="outerMethod" />
557         """))
558         return self.assertFlattensTo(element, "<p>Hello, world.</p>")
559
560
561     def test_sameLoaderTwice(self):
562         """
563         Rendering the output of a loader, or even the same element, should
564         return different output each time.
565         """
566         sharedLoader = XMLString(
567             '<p xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1">'
568             '<t:transparent t:render="classCounter" /> '
569             '<t:transparent t:render="instanceCounter" />'
570             '</p>')
571
572         class DestructiveElement(Element):
573             count = 0
574             instanceCount = 0
575             loader = sharedLoader
576
577             @renderer
578             def classCounter(self, request, tag):
579                 DestructiveElement.count += 1
580                 return tag(str(DestructiveElement.count))
581             @renderer
582             def instanceCounter(self, request, tag):
583                 self.instanceCount += 1
584                 return tag(str(self.instanceCount))
585
586         e1 = DestructiveElement()
587         e2 = DestructiveElement()
588         self.assertFlattensImmediately(e1, "<p>1 1</p>")
589         self.assertFlattensImmediately(e1, "<p>2 2</p>")
590         self.assertFlattensImmediately(e2, "<p>3 1</p>")
591
592
593
594 class TagLoaderTests(FlattenTestCase):
595     """
596     Tests for L{TagLoader}.
597     """
598     def setUp(self):
599         self.loader = TagLoader(tags.i('test'))
600
601
602     def test_interface(self):
603         """
604         An instance of L{TagLoader} provides L{ITemplateLoader}.
605         """
606         self.assertTrue(verifyObject(ITemplateLoader, self.loader))
607
608
609     def test_loadsList(self):
610         """
611         L{TagLoader.load} returns a list, per L{ITemplateLoader}.
612         """
613         self.assertIsInstance(self.loader.load(), list)
614
615
616     def test_flatten(self):
617         """
618         L{TagLoader} can be used in an L{Element}, and flattens as the tag used
619         to construct the L{TagLoader} would flatten.
620         """
621         e = Element(self.loader)
622         self.assertFlattensImmediately(e, '<i>test</i>')
623
624
625
626 class TestElement(Element):
627     """
628     An L{Element} that can be rendered successfully.
629     """
630     loader = XMLString(
631         '<p xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1">'
632         'Hello, world.'
633         '</p>')
634
635
636
637 class TestFailureElement(Element):
638     """
639     An L{Element} that can be used in place of L{FailureElement} to verify
640     that L{renderElement} can render failures properly.
641     """
642     loader = XMLString(
643         '<p xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1">'
644         'I failed.'
645         '</p>')
646
647     def __init__(self, failure, loader=None):
648         self.failure = failure
649
650
651
652 class FailingElement(Element):
653     """
654     An element that raises an exception when rendered.
655     """
656     def render(self, request):
657         a = 42
658         b = 0
659         return a / b
660
661
662
663 class FakeSite(object):
664     """
665     A minimal L{Site} object that we can use to test displayTracebacks
666     """
667     displayTracebacks = False
668
669
670
671 class TestRenderElement(TestCase):
672     """
673     Test L{renderElement}
674     """
675
676     def setUp(self):
677         """
678         Set up a common L{DummyRequest} and L{FakeSite}.
679         """
680         self.request = DummyRequest([""])
681         self.request.site = FakeSite()
682
683
684     def test_simpleRender(self):
685         """
686         L{renderElement} returns NOT_DONE_YET and eventually
687         writes the rendered L{Element} to the request before finishing the
688         request.
689         """
690         element = TestElement()
691
692         d = self.request.notifyFinish()
693
694         def check(_):
695             self.assertEqual(
696                 "".join(self.request.written),
697                 "<!DOCTYPE html>\n"
698                 "<p>Hello, world.</p>")
699             self.assertTrue(self.request.finished)
700
701         d.addCallback(check)
702
703         self.assertIdentical(NOT_DONE_YET, renderElement(self.request, element))
704
705         return d
706
707
708     def test_simpleFailure(self):
709         """
710         L{renderElement} handles failures by writing a minimal
711         error message to the request and finishing it.
712         """
713         element = FailingElement()
714
715         d = self.request.notifyFinish()
716
717         def check(_):
718             flushed = self.flushLoggedErrors(FlattenerError)
719             self.assertEqual(len(flushed), 1)
720             self.assertEqual(
721                 "".join(self.request.written),
722                 ('<!DOCTYPE html>\n'
723                  '<div style="font-size:800%;'
724                  'background-color:#FFF;'
725                  'color:#F00'
726                  '">An error occurred while rendering the response.</div>'))
727             self.assertTrue(self.request.finished)
728
729         d.addCallback(check)
730
731         self.assertIdentical(NOT_DONE_YET, renderElement(self.request, element))
732
733         return d
734
735
736     def test_simpleFailureWithTraceback(self):
737         """
738         L{renderElement} will render a traceback when rendering of
739         the element fails and our site is configured to display tracebacks.
740         """
741         self.request.site.displayTracebacks = True
742
743         element = FailingElement()
744
745         d = self.request.notifyFinish()
746
747         def check(_):
748             flushed = self.flushLoggedErrors(FlattenerError)
749             self.assertEqual(len(flushed), 1)
750             self.assertEqual(
751                 "".join(self.request.written),
752                 "<!DOCTYPE html>\n<p>I failed.</p>")
753             self.assertTrue(self.request.finished)
754
755         d.addCallback(check)
756
757         renderElement(self.request, element, _failElement=TestFailureElement)
758
759         return d
760
761
762     def test_nonDefaultDoctype(self):
763         """
764         L{renderElement} will write the doctype string specified by the
765         doctype keyword argument.
766         """
767
768         element = TestElement()
769
770         d = self.request.notifyFinish()
771
772         def check(_):
773             self.assertEqual(
774                 "".join(self.request.written),
775                 ('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"'
776                  ' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n'
777                  '<p>Hello, world.</p>'))
778
779         d.addCallback(check)
780
781         renderElement(
782             self.request,
783             element,
784             doctype=(
785                 '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"'
786                 ' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'))
787
788         return d
789
790
791     def test_noneDoctype(self):
792         """
793         L{renderElement} will not write out a doctype if the doctype keyword
794         argument is C{None}.
795         """
796
797         element = TestElement()
798
799         d = self.request.notifyFinish()
800
801         def check(_):
802             self.assertEqual(
803                 "".join(self.request.written),
804                 '<p>Hello, world.</p>')
805
806         d.addCallback(check)
807
808         renderElement(self.request, element, doctype=None)
809
810         return d