Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

320

321

322

323

324

325

326

327

328

329

330

331

332

333

334

335

336

337

338

339

340

341

342

343

344

345

346

347

348

349

350

351

352

353

354

355

356

357

358

359

360

361

362

363

364

365

366

367

368

369

370

371

372

373

374

375

376

377

378

379

380

381

382

# -*- coding: UTF-8 -*- 

# Adapted copy from lxml\src\lxml\html\builder.py 

# -------------------------------------------------------------------- 

# The ElementTree toolkit is 

# Copyright (c) 1999-2004 by Fredrik Lundh 

# Modifications in this file are 

# Copyright (c) 2012-2016 Luc Saffre 

# -------------------------------------------------------------------- 

 

# This document is part of the Lino test suite. To test only this 

# document, run:: 

# 

#   $ python setup.py test -s tests.UtilsTests.test_xmlgen_html 

 

"""Defines an ElementTree Builder for generating HTML documents. 

 

Usage: 

 

>>> from lino.utils.xmlgen.html import E 

>>> html = E.html( 

...            E.head( E.title("Hello World") ), 

...            E.body( 

...              E.h1("Hello World !"), 

...              class_="main" 

...            ) 

...        ) 

 

>>> print E.tostring_pretty(html) 

<html> 

<head> 

<title>Hello World</title> 

</head> 

<body class="main"> 

<h1>Hello World !</h1> 

</body> 

</html> 

 

 

>>> kw = dict(title=u'Ein süßes Beispiel') 

>>> kw.update(href="foo/bar.html") 

>>> btn = E.button(type='button', class_='x-btn-text x-tbar-upload') 

>>> html = E.a(btn, **kw) 

>>> print E.tostring_pretty(html) 

<a href="foo/bar.html" title="Ein s&#252;&#223;es Beispiel"> 

<button class="x-btn-text x-tbar-upload" type="button" /> 

</a> 

 

You can also do the opposite, i.e. parse HTML: 

 

>>> html = E.raw('''<a href="foo/bar.html" 

... title="Ein s&#252;&#223;es Beispiel"> 

... <button class="x-btn-text x-tbar-upload" type="button" /> 

... </a>''') 

>>> print(E.tostring_pretty(html)) 

<a href="foo/bar.html" title="Ein s&#252;&#223;es Beispiel"> 

<button class="x-btn-text x-tbar-upload" type="button" /> 

</a> 

 

 

>>> print(E.tostring(E.raw( 

...     '<ul type="disc"><li>First</li><li>Second</li></ul>'))) 

<ul type="disc"><li>First</li><li>Second</li></ul> 

 

""" 

 

from __future__ import unicode_literals 

from builtins import str 

from builtins import object 

 

import types 

from xml.etree import ElementTree as ET 

from lxml.etree import HTML 

 

from lino.utils import join_elems 

from lino.utils.xmlgen import Namespace 

from lino.utils.html2rst import html2rst 

# from htmlentitydefs import name2codepoint 

 

# ENTITIES = {} 

# ENTITIES.update((x, unichr(i)) for x, i in name2codepoint.iteritems()) 

 

 

# def CreateParser(): 

#     """Every string that is being parsed must get its own parser instance. 

#     This is because "Due to limitations in the Expat library used by 

#     pyexpat, the xmlparser instance returned can only be used to parse 

#     a single XML document. Call ParserCreate for each document to 

#     provide unique parser instances. (`docs.python.org 

#     <https://docs.python.org/2/library/pyexpat.html>`_) 

 

#     """ 

#     p = ET.XMLParser() 

#     # PARSER.entity.update(htmlentitydefs.entitydefs) 

#     p.entity = ENTITIES 

#     assert 'otilde' in p.entity 

#     assert 'eacute' in p.entity 

#     assert 'nbsp' in p.entity 

#     return p 

 

# class RAW_HTML_STRING(unicode): 

#     pass 

 

 

class HtmlNamespace(Namespace): 

    """The HTML namespace. 

    This is instantiated as ``E``. 

    """ 

 

    def tostring(self, v, *args, **kw): 

        # if isinstance(v, types.GeneratorType): 

        if isinstance(v, (types.GeneratorType, list, tuple)): 

            return "".join([self.tostring(x, *args, **kw) for x in v]) 

        if self.iselement(v): 

            # kw.setdefault('method', 'html') 

            return super(HtmlNamespace, self).tostring(v, *args, **kw) 

        return str(v) 

 

    def to_rst(self, v, stripped=True): 

        if isinstance(v, types.GeneratorType): 

            return "".join([self.to_rst(x, stripped) for x in v]) 

        if E.iselement(v): 

            return html2rst(v, stripped) 

        return str(v) 

 

    # def raw(self, raw_html): 

    #     return RAW_HTML_STRING(raw_html) 

 

    def raw(self, raw_html): 

        """Parses the given string into an HTML Element.""" 

        # print 20151008, raw_html 

 

        # the lxml parser wraps `<html><body>...</body></html>` around 

        # the snippet, but we don't want it. 

        return HTML(raw_html)[0][0] 

        # try: 

        #     return self.fromstring(raw_html, parser=CreateParser()) 

        # except ET.ParseError as e: 

        #     raise Exception("ParseError {0} in {1}".format(e, raw_html)) 

 

 

E = HtmlNamespace(None, """ 

a 

abbr 

acronym 

address 

alt 

applet 

area 

b 

base 

basefont 

bdo 

big 

blockquote 

body 

br 

button 

caption 

center 

cite      

code      

col       

colgroup  

dd        

del       

dfn       

dir       

div       

dl        

dt        

em        

fieldset  

font      

form      

frame     

frameset  

h1      

h2      

h3      

h4      

h5 

h6 

head 

height 

hr      

html    

i       

iframe  

img     

input   

ins     

isindex  

kbd  

label  

legend  

li  

link  

map  

menu  

meta  

name 

noframes  

noscript  

object  

ol  

optgroup  

option  

p 

param  

pre  

q  

s  

samp 

script 

select 

small 

span 

strike 

strong 

style 

sub 

sup 

table 

tbody 

td 

textarea 

tfoot 

th 

thead 

title 

tr 

tt 

u 

ul 

var 

 

class 

id 

bgcolor 

cellspacing 

width 

align 

valign 

href 

type 

rel 

target 

value 

onclick 

src 

rows 

data-toggle 

tabindex 

placeholder 

""") 

 

 

def table_header_row(*headers, **kw): 

    return E.tr(*[E.th(h, **kw) for h in headers]) 

 

 

def table_body_row(*cells, **kw): 

    return E.tr(*[E.td(h, **kw) for h in cells]) 

 

 

class Table(object): 

    """A pythonic representation of a ``<table>`` with ``<head>``, 

    ``<foot>`` and ``<body>``. 

 

    """ 

    def __init__(self): 

        self.clear() 

 

    def clear(self): 

        self.head = [] 

        self.foot = [] 

        self.body = [] 

        self.attrib = dict() 

 

    def add_header_row(self, *args, **kw): 

        e = table_header_row(*args, **kw) 

        self.head.append(e) 

        return e 

 

    def add_footer_row(self, *args, **kw): 

        e = table_body_row(*args, **kw) 

        self.foot.append(e) 

        return e 

 

    def add_body_row(self, *args, **kw): 

        e = table_body_row(*args, **kw) 

        self.body.append(e) 

        return e 

 

    def as_element(self): 

        children = [] 

        if self.head: 

            children.append(E.thead(*self.head)) 

        if self.foot: 

            children.append(E.tfoot(*self.foot)) 

        if self.body: 

            children.append(E.tbody(*self.body)) 

        return E.table(*children, **self.attrib) 

 

 

class Document(object): 

    """A pythonic representation of a ``<body>`` with a ``<title>`` and 

    some ``<head>`` tags for stylesheets. 

 

    """ 

 

    def __init__(self, title, stylesheets=None): 

        self.title = title 

        self.body = [] 

        self.stylesheets = stylesheets or [] 

 

    def add_stylesheet(self, url): 

        self.stylesheets.append(url) 

 

    def add_table(self): 

        t = Table() 

        self.body.append(t) 

        return t 

 

    def write(self, *args, **kw): 

        ET.ElementTree(self.as_element()).write(*args, **kw) 

 

    def as_element(self): 

        body = [] 

        for e in self.body: 

            if isinstance(e, Table): 

                body.append(e.as_element()) 

            else: 

                body.append(e) 

        headers = [] 

        for css in self.stylesheets: 

            headers.append(E.link(rel="stylesheet", type="text/css", href=css)) 

        headers.append(E.title(self.title)) 

 

        return E.html( 

            E.head(*headers), 

            E.body(*body) 

        ) 

 

 

def lines2p(lines, min_height=0, **attrs): 

    """Convert the given list of text lines `lines` into a paragraph 

(``<p>``) with one ``<br>`` between each line. If optional 

`min_height` is given, add empty lines if necessary. 

 

    Examples: 

 

    >>> print(E.tostring(lines2p(['first', 'second']))) 

    <p>first<br />second</p> 

 

    >>> print(E.tostring(lines2p(['first', 'second'], min_height=5))) 

    <p>first<br />second<br /><br /><br /></p> 

 

    If `min_height` is specified, and `lines` contains more items, 

    then we don't truncate: 

 

    >>> print(E.tostring(lines2p(['a', 'b', 'c', 'd', 'e'], min_height=4))) 

    <p>a<br />b<br />c<br />d<br />e</p> 

 

    This also works: 

 

    >>> print(E.tostring(lines2p([], min_height=5))) 

    <p><br /><br /><br /><br /></p> 

 

    """ 

    while len(lines) < min_height: 

        lines.append('') 

    lines = join_elems(lines, E.br) 

    return E.p(*lines, **attrs) 

 

 

def _test(): 

    import doctest 

    doctest.testmod() 

 

if __name__ == "__main__": 

    _test()