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

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

# Copyright 2011-2015 Luc Saffre 

# License: BSD (see file COPYING for details) 

 

"""Inspired by Frederik Lundh's 

`ElementTree Builder 

<http://effbot.org/zone/element-builder.htm>`_ 

 

.. autosummary:: 

   :toctree: 

 

   html 

   intervat 

   odf 

   cbss 

   sepa 

 

 

>>> E = Namespace('http://my.ns', 

...    "bar baz bar-o-baz foo-bar class def") 

 

>>> bob = E.bar_o_baz() 

>>> baz = E.add_child(bob, 'baz', class_='first') 

>>> print E.tostring(baz) 

<baz xmlns="http://my.ns" class="first" /> 

 

>>> bob = E.bar_o_baz('Hello', class_='first', foo_bar="3") 

>>> print E.tostring(bob) 

<bar-o-baz xmlns="http://my.ns" class="first" foo-bar="3">Hello</bar-o-baz> 

 

The following reproduces a pifall. Here is the initial code: 

 

>>> E = Namespace(None, "div br") 

>>> bob = E.div("a", E.br(), "b", E. br(), "c", E.br(), "d") 

>>> print E.tostring(bob) 

<div>a<br />b<br />c<br />d</div> 

 

The idea is to use `join_elems` to insert the <br> tags: 

 

>>> from lino.utils import join_elems 

 

But surprise: 

 

>>> elems = join_elems(["a", "b", "c", "d"], sep=E.br()) 

>>> print E.tostring(E.div(*elems)) 

<div>a<br />bcd<br />bcd<br />bcd</div> 

 

What happened here is that the same `<br>` element instance was being 

inserted multiple times at different places.  The correct usage is 

without the parentheses so that `join_elems` instantiates each time a 

new element: 

 

>>> elems = join_elems(["a", "b", "c", "d"], sep=E.br) 

>>> print E.tostring(E.div(*elems)) 

<div>a<br />b<br />c<br />d</div> 

 

""" 

from builtins import str 

from past.builtins import basestring 

from builtins import object 

import six 

 

import logging 

logger = logging.getLogger(__name__) 

 

 

import datetime 

from functools import partial 

 

from lino.utils.xmlgen import etree 

#~ from lino.utils import Warning 

from django.utils.functional import Promise 

from django.utils.encoding import force_text 

 

 

def pretty_print(elem): 

    """Return a pretty-printed XML string for the Element. 

    """ 

    return prettify(etree.tostring(elem, 'utf-8')) 

    # the following also indented: 

    # from http://renesd.blogspot.com/2007/05/pretty-print-xml-with-python.html 

    # via http://broadcast.oreilly.com/2010/03/pymotw-creating-xml-documents.html 

    #~ from xml.dom import minidom 

    #~ rough_string = etree.tostring(elem, 'utf-8') 

    #~ reparsed = minidom.parseString(rough_string) 

    #~ return reparsed.toprettyxml(indent="  ") 

 

 

def prettify(s): 

    return s.replace('><', '>\n<') 

 

 

def compatstr(s): 

    """The `python-future <http://python-future.org/>`__ package 

    introduces a special helper class `newstr` which simulates, under 

    Python 2, the behaviour of Python 3 strings.  But 

    `xml.etree.ElementTree 

    <https://docs.python.org/2/library/xml.etree.elementtree.html>`__ 

    in Python 2 doesn't know about `python-future` and produces 

    invalid XML when you feed it with such a string. 

 

    So this function converts any `newstr` back to a real newstr. 

 

    TODO: Not yet tested under Python 3. At the best it is just 

    unefficient. 

 

    """ 

    if isinstance(s, str): 

        return six.text_type(s) 

    return s 

 

RESERVED_WORDS = frozenset(""" 

and       del       from      not       while 

as        elif      global    or        with 

assert    else      if        pass      yield 

break     except    import    print 

class     exec      in        raise 

continue  finally   is        return 

def       for       lambda    try 

""".split()) 

 

TYPEMAP = { 

    #~ datetime.datetime: py2str, 

    #~ IncompleteDate : lambda e,v : str(v), 

    datetime.datetime: lambda e, v: v.strftime("%Y%m%dT%H%M%S"), 

    datetime.date: lambda e, v: v.strftime("%Y-%m-%d"), 

    int: lambda e, v: str(v), 

} 

 

 

class Namespace(object): 

    """An XML namespace.  Base class for 

    :class:`lino.utils.xmlgen.html.HtmlNamespace` and the namespaces 

    defined in :mod:`lino.utils.xmlgen.intervat`. 

 

    """ 

    prefix = None 

    targetNamespace = None 

    names = None 

 

    def __init__(self, targetNamespace=None, names=None, prefix=None): 

        #~ if prefix is not None: 

            #~ self.prefix = prefix 

        #~ kw.setdefault('typemap',TYPEMAP) 

        #~ kw.setdefault('makeelement',self.makeelement) 

        #~ nsmap = kw.setdefault('nsmap',{}) 

 

        if prefix is not None: 

            self.prefix = prefix 

        if names is not None: 

            self.names = names 

        if targetNamespace is not None: 

            self.targetNamespace = targetNamespace 

        if self.targetNamespace is not None: 

            #~ kw.update(namespace=self.targetNamespace) 

 

            self._ns = '{' + self.targetNamespace + '}' 

            if self.prefix is not None: 

                etree.register_namespace(self.prefix, self.targetNamespace) 

            #~ if prefix: 

            #~ nsmap[prefix] = self.targetNamespace 

        #~ if used_namespaces is not None: 

            #~ self.used_namespaces = used_namespaces 

        #~ if self.used_namespaces is not None: 

            #~ for ns in self.used_namespaces: 

                #~ nsmap[ns.prefix] = ns.targetNamespace 

        #~ self._element_maker = ElementMaker(**kw) 

        #~ self._source_elements = {} 

        if self.names is not None: 

            self.define_names(self.names) 

        self.setup_namespace() 

 

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

        return etree.iselement(*args, **kw) 

 

    def setup_namespace(self): 

        pass 

 

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

        class dummy(object): 

            pass 

        data = [] 

        file = dummy() 

        file.write = data.append 

        if self.targetNamespace is not None: 

            kw.setdefault('default_namespace', self.targetNamespace) 

        etree.ElementTree(element).write(file, *args, **kw) 

        return "".join(data) 

 

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

        #~ kw.setdefault('xml_declaration',False) 

        #~ kw.setdefault('encoding','utf-8') 

        #~ kw.update(xml_declaration=False) 

        #~ kw.update(encoding='utf-8') 

        s = self.tostring(*args, **kw) 

        #~ return s 

        #~ return minidom.parseString(s).toprettyxml(indent="  ") 

        return prettify(s) 

 

    def addns(self, tag): 

        if self.targetNamespace is None or tag[0] == "{": 

            return tag 

        return self._ns + tag 

 

    def makeattribs(self, **kw): 

        #~ ns = self._element_maker._namespace 

        #~ if ns is None: return kw 

        xkw = dict() 

        for k, v in list(kw.items()): 

            k = getattr(self, k).args[0]  # convert iname to tagname 

            xkw[self.addns(compatstr(k))] = compatstr(v) 

        return xkw 

 

    def create_element(self, tag, *children, **attrib): 

        #~ if tag == 'div': 

            #~ logger.info("20130805 create_element %s",children) 

        nsattrib = self.makeattribs(**attrib) 

        tag = self.addns(tag) 

        elem = etree.Element(tag, nsattrib) 

        for item in children: 

            if isinstance(item, Promise): 

                item = force_text(item) 

            if isinstance(item, dict): 

                elem.attrib.update(self.makeattribs(**item)) 

            elif isinstance(item, basestring): 

                #~ if len(elem) and len(elem[-1]) == 0: 

                if len(elem): 

                    last = elem[-1] 

                    last.tail = (last.tail or "") + item 

                else: 

                    elem.text = (elem.text or "") + item 

            elif etree.iselement(item): 

                elem.append(item) 

            else: 

                raise TypeError("bad argument: %r" % item) 

            #~ print "20130805 added %s --> %s" % (item,self.tostring(elem)) 

        return elem 

 

    def define_names(self, names): 

        for tag in names.split(): 

            iname = tag.replace("-", "_") 

            iname = iname.replace(".", "_") 

            #~ if iname in ('class','for','in','def'): 

            if iname in RESERVED_WORDS: 

                iname += "_" 

            #~ setattr(self,iname,getattr(self._element_maker,name)) 

            p = partial(self.create_element, tag) 

            setattr(self, iname, p) 

 

    def getnsattr(self, elem, name): 

        #~ if self.targetNamespace is None or name.startswith('{'): 

            #~ return elem.get(name) 

        return elem.get(self._element_maker._namespace + name) 

 

    #~ def update_attribs(self,root,**kw): 

    def update(self, root, **kw): 

        root.attrib.update(self.makeattribs(**kw)) 

 

    def add_child(self, parent, _name, *args, **kw): 

        ecl = getattr(self, _name) 

        #~ kw = self.makeattribs(**kw) 

        #~ print 20120420, kw 

        e = ecl(*args, **kw) 

        parent.append(e) 

        return e 

 

    def fromstring(self, s, **kwargs): 

        """Build an element tree from the given XML source string. 

 

        This just forwards to the 

        :meth:`xml.etree.ElementTree.fromstring` library function. 

        See the `Parsing XML 

        <https://docs.python.org/2.7/library/xml.etree.elementtree.html#parsing-xml>`__ 

        section of the Python docs. 

 

        """ 

        return etree.etree.fromstringlist([s], **kwargs) 

 

    def raw(self, *args): 

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

        return RAW(*args) 

 

RAW = etree.XML 

 

 

def _test(): 

    import doctest 

    doctest.testmod() 

 

if __name__ == "__main__": 

    _test()