File: __init__.py

package info (click to toggle)
python-wither 1.1-3
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, sid, trixie
  • size: 128 kB
  • sloc: python: 117; makefile: 3
file content (285 lines) | stat: -rwxr-xr-x 9,179 bytes parent folder | download | duplicates (2)
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
#!/usr/bin/env python
"""Wither: Simple python DSL for writing XML/HTML

Wither is implemented as a thin stateless wrapper around lxml/etree nodes to 
provide a convient interface to create and update elements. the actual tree of 
nodes/elements is created and managed by etree objects and any node's backing 
object can be retrived by calling :py:func:`etree` on the node. this allows you 
to use standard etree functions such as tostring on the document or to retrive 
the more advanced node iterators

If you have an etree node that you wish to add elements to simply call 
:py:class:`Node` (etree_node) with the etree object procded as the first 
argument
"""
__author__ = "Da_Blitz"
__email__ = "code@pocketnix.org"
__url__ = "http://code.pocketnix.org/wither"
__version__ = "1.1"

import lxml.etree as _etree

class Node(object):
    """Node tags, attributes and text are prefixed with "node\_" to avoid potential 
    name conflicts with automatic object create. ie you may wish to create a 
    node called :py:meth:`attrib` or :py:meth:`text` or :py:meth:`tag` 
    however they would have been shadowed by the nodes properties
    
    to update xml attributes the prefered form is to use the python mapping 
    interface (ie obj[key]) on the node, however the attributes of the xml 
    fragment are also made avalible at node.node_attrib if you require the more 
    advanced dictonary methods such as itertion interfaces or dict.update()

    # enable XML literals in doctest output
    
    >>> import lxml.usedoctest

    Setting Attributes:

    Below is an example of updating the 'href' attribute in a link
    
    >>> a = Node('a')
    >>> a['href'] = 'http://www.example.org'
    >>> print(a)
    <a href="http://www.example.org"/>
    
    We also support setting kwargs via calling the proxy or in the proxy's 
    constructor, note that this returns the proxy itself for use inside a 
    'with' block
    
    >>> a = Node('a', href='http://www.example.org')
    >>> print(a)
    <a href="http://www.example.org"/>
    >>> a(href='http://www.example.com') # now change .org to .com
    <wither.Node object at ...>
    >>> print(a)
    <a href="http://www.example.com"/>
    
    Setting Text Nodes:

    this can be combined with setting of the text node via '==' (we use
    '==' instead of '=' as objects cannot override the assignment opperator)
    
    >>> a = Node('a')
    >>> print(a(href='http://www.example.org') == 'RFC2606 compliant')
    <a href="http://www.example.org">RFC2606 compliant</a>
    
    Sub Nodes/Elements:

    To Build new child nodes off of the current node, you can use standard 
    attribute access as shown below, repeated access to the same attribute 
    returns NEW objects, not the first one generated
    
    >>> div = Node('div')
    >>> div.p == 'this is the first paragraph'
    <...>
    >>> div.p == 'this is an entirely new (2nd) paragraph'
    <...>
    >>> print(div)
    <div><p>this is the first paragraph</p><p>this is an entirely new (2nd) 
    paragraph </p></div>
    
    With Blocks:

    With blocks can be used to add depth to a generated XML document. there is 
    no hard coded limit to the depth of the with statements, however for clarity 
    it is recommended that leaf nodes are created not via 'with parent.leaf as 
    leaf' but instead as 'parent.leaf(\*\*attribs) == "text"' to make leaf nodes 
    visually distinct and reduce the amount of indentation
    
    >>> n = Node('html')
    >>> with n.head as head:
    ...     head.title == 'Wither DocString Example'
    ...     head.link(href='http://www.example.org/', rel='homepage')
    <...>
    >>> print(n)
    <html><head><title>Wither DocString Example</title><link 
    href="http://www.example.org/" rel="homepage"/></head></html>
    """
    __slots__ = ('_node',)
    def __init__(self, tag, **attribs):
        """Node objects can be created by specifying tag as a string or by 
        providing a pre-created etree object to be used as the backing store. 
        the second form is mainly intended to convert a descendent etree node 
        back into a Node object for further manipulation of the tree after 
        performing an etree operation on a ancestor node
        
        :param tag: the tag to use as the tag for the etree backing store or a 
        prebuild etree object
        :type tag: str or etree.Element
        :param attrib: XML attributes specfied in the python keyword argument 
        format (href='http://www.example.org')
        """
        if isinstance(tag, str):
            self._node = _etree.Element(tag)
        else:
            self._node = tag
        
        self._node.attrib.update(attribs)
        
    @property
    def node_attrib(self):
        """Alternate way to add/set/delete XML attributes or access more 
        advanced dictonary methods for the attirbutes (eg 
        node_attrib.update()"""
        return self._node.attrib
    
    @property
    def node_tag(self):
        """The nodes tag property, eg 'div' for <div /> or 'a' for html 
        links"""
        return self._node.tag
    
    @property
    def node_text(self):
        """Allows setting of the XML tag's text property. eg <a 
        href="#link">The text property goes in here</a>
        """
        return self._node.text
        
    @node_text.setter
    def node_text(self, val):
        self._node.text = val

    def append(self, node):
        """Allows adding of foreign trees as children of this node
        
        >>> div = Node('div')
        >>> div.append(Node('p'))
        >>> len(div)
        1
        
        :param node Node: The node/foreign tree to be added
        """
        node = etree(node)
        self._node.append(node)
    
    def __getitem__(self, key):
        """Proxy item requests to self.node_attrib
        >>> n = Node('a')
        >>> n['href'] = 'http://www.example.org'
        >>> n['href']
        'http://www.example.org'
        """
        return self._node.attrib[key]
        
    def __setitem__(self, key, val):
        """Proxy item requests to self.node_attrib
        >>> n = Node('a')
        >>> n['href'] = 'http://www.example.org'
        >>> n['href']
        'http://www.example.org'
        """
        self._node.attrib[key] = val
        
    def __delitem__(self, key):
        """Proxy item deletions to self.node_attrib
        >>> n = Node('a')
        >>> n['href'] = 'http://www.example.org'
        >>> n['href']
        'http://www.example.org'
        >>> del n['href']
        >>> n['href']
        Traceback (most recent call last):
        KeyError: 'href'
        """
        del self._node.attrib[key]
        
    def __getattr__(self, tag):
        """Dynamically create Node objects with a tag name identical to that of 
        the requested attribute
        
        >>> n = Node('div')
        >>> n.p
        <wither.Node object at ...>
        """
        e_node = _etree.Element(tag)
        self._node.append(e_node)

        node = Node(e_node)

        return node
    
    def __enter__(self):
        """ 
        >>> with Node('body') as body:
        ...     pass
        """
        return self
        
    def __exit__(self, *tb):
        pass
        
    def __call__(self, **attribs):
        """ 
        >>> a = Node('a')(href='http://www.example.org')
        >>> a['href']
        'http://www.example.org'
        
        :param attrib: XML attributes specfied in the python keyword argument 
        :returns: returns itself for use in a 'with' block
        :rtype: Node
        """
        self.node_attrib.update(attribs)
        
        return self

    def __eq__(self, other):
        """ 
        >>> a = Node('a')
        >>> a == 'My Text'
        <wither.Node object at ...>
        >>> a.node_text
        'My Text'

        :returns: returns itself for use in a 'with' block
        :rtype: Node
        """
        self.node_text = other
        
        return self

    def __str__(self):
        """ 
        >>> a = Node('a')(href='http://www.example.org')
        >>> print(a)
        <a href="http://www.example.org"/>
        """
        return _etree.tostring(self._node, encoding='unicode')
    
    def __etree__(self):
        ## Tested in wither.etree() command
        return self._node

    def __len__(self):
        """ 
        >>> div = Node('div')
        >>> div.p
        <wither.Node object at ...>
        >>> div.p
        <wither.Node object at ...>
        >>> len(div)
        2

        :returns: returns the ammount of child nodes
        :rtype: Int
        """
        return len(self._node)
        
def etree(node):
    """Convert a Node to an etree object
    
    operations on the returned node will update the generated XML document
    
    to convert the etree object back to a node use :py:class:`Node` (etree)
    
    >>> n = Node('a')(href='http://www.example.org') == "RFC2606 domain"
    >>> etree(n)
    <Element a at ...>

    :param Node node: wither.Node object to convert to an etree.Element object
    :returns: etree Backing store for node
    :rtype: etree.Element
    """
    
    return node.__etree__()