Codebase list pyquery / fccf43b
Merge pull request #195 from whybin/serialize Serialize form elements Gael Pasgrimaud authored 5 years ago GitHub committed 5 years ago
2 changed file(s) with 286 addition(s) and 3 deletion(s). Raw diff Collapse all Expand all
33 #
44 # Distributed under the BSD license, see LICENSE.txt
55 from .cssselectpatch import JQueryTranslator
6 from collections import OrderedDict
67 from .openers import url_opener
78 from .text import extract_text
89 from copy import deepcopy
423424 """return the xml root element
424425 """
425426 if self._parent is not no_default:
426 return self._parent.getroottree()
427 return self._parent[0].getroottree()
427428 return self[0].getroottree()
428429
429430 @property
10381039 return 'on'
10391040 else:
10401041 return val
1041 # <input> and everything else.
1042 # <input>
1043 elif tag.tag == 'input':
1044 val = self._copy(tag).attr('value')
1045 return val.replace('\n', '') if val else ''
1046 # everything else.
10421047 return self._copy(tag).attr('value') or ''
10431048
10441049 def _set_value(pq, value):
15171522 setattr(PyQuery, name, fn)
15181523 fn = Fn()
15191524
1525
1526 ########
1527 # AJAX #
1528 ########
1529
1530 @with_camel_case_alias
1531 def serialize_array(self):
1532 """Serialize form elements as an array of dictionaries, whose structure
1533 mirrors that produced by the jQuery API. Notably, it does not handle the
1534 deprecated `keygen` form element.
1535
1536 >>> d = PyQuery('<form><input name="order" value="spam"></form>')
1537 >>> d.serialize_array() == [{'name': 'order', 'value': 'spam'}]
1538 True
1539 >>> d.serializeArray() == [{'name': 'order', 'value': 'spam'}]
1540 True
1541 """
1542 return list(map(
1543 lambda p: {'name': p[0], 'value': p[1]},
1544 self.serialize_pairs()
1545 ))
1546
1547 def serialize(self):
1548 """Serialize form elements as a URL-encoded string.
1549
1550 >>> h = (
1551 ... '<form><input name="order" value="spam">'
1552 ... '<input name="order2" value="baked beans"></form>'
1553 ... )
1554 >>> d = PyQuery(h)
1555 >>> d.serialize()
1556 'order=spam&order2=baked%20beans'
1557 """
1558 return urlencode(self.serialize_pairs()).replace('+', '%20')
1559
1560
15201561 #####################################################
15211562 # Additional methods that are not in the jQuery API #
15221563 #####################################################
1564
1565 @with_camel_case_alias
1566 def serialize_pairs(self):
1567 """Serialize form elements as an array of 2-tuples conventional for
1568 typical URL-parsing operations in Python.
1569
1570 >>> d = PyQuery('<form><input name="order" value="spam"></form>')
1571 >>> d.serialize_pairs()
1572 [('order', 'spam')]
1573 >>> d.serializePairs()
1574 [('order', 'spam')]
1575 """
1576 # https://github.com/jquery/jquery/blob
1577 # /2d4f53416e5f74fa98e0c1d66b6f3c285a12f0ce/src/serialize.js#L14
1578 _submitter_types = ['submit', 'button', 'image', 'reset', 'file']
1579
1580 controls = self._copy([])
1581 # Expand list of form controls
1582 for el in self.items():
1583 if el[0].tag == 'form':
1584 form_id = el.attr('id')
1585 if form_id:
1586 # Include inputs outside of their form owner
1587 root = self._copy(el.root.getroot())
1588 controls.extend(root(
1589 '#%s :not([form]):input, [form="%s"]:input'
1590 % (form_id, form_id)))
1591 else:
1592 controls.extend(el(':not([form]):input'))
1593 elif el[0].tag == 'fieldset':
1594 controls.extend(el(':input'))
1595 else:
1596 controls.extend(el)
1597 # Filter controls
1598 selector = '[name]:enabled:not(button)' # Not serializing image button
1599 selector += ''.join(map(
1600 lambda s: ':not([type="%s"])' % s,
1601 _submitter_types))
1602 controls = controls.filter(selector)
1603
1604 def _filter_out_unchecked(_, el):
1605 el = controls._copy(el)
1606 return not el.is_(':checkbox:not(:checked)') \
1607 and not el.is_(':radio:not(:checked)')
1608 controls = controls.filter(_filter_out_unchecked)
1609
1610 # jQuery serializes inputs with the datalist element as an ancestor
1611 # contrary to WHATWG spec as of August 2018
1612 #
1613 # xpath = 'self::*[not(ancestor::datalist)]'
1614 # results = []
1615 # for tag in controls:
1616 # results.extend(tag.xpath(xpath, namespaces=controls.namespaces))
1617 # controls = controls._copy(results)
1618
1619 # Serialize values
1620 ret = []
1621 for field in controls:
1622 val = self._copy(field).val()
1623 if isinstance(val, list):
1624 ret.extend(map(
1625 lambda v: (field.attrib['name'], v.replace('\n', '\r\n')),
1626 val
1627 ))
1628 else:
1629 ret.append((field.attrib['name'], val.replace('\n', '\r\n')))
1630 return ret
1631
1632 @with_camel_case_alias
1633 def serialize_dict(self):
1634 """Serialize form elements as an ordered dictionary. Multiple values
1635 corresponding to the same input name are concatenated into one list.
1636
1637 >>> d = PyQuery('''<form>
1638 ... <input name="order" value="spam">
1639 ... <input name="order" value="eggs">
1640 ... <input name="order2" value="ham">
1641 ... </form>''')
1642 >>> d.serialize_dict()
1643 OrderedDict([('order', ['spam', 'eggs']), ('order2', 'ham')])
1644 >>> d.serializeDict()
1645 OrderedDict([('order', ['spam', 'eggs']), ('order2', 'ham')])
1646 """
1647 ret = OrderedDict()
1648 for name, val in self.serialize_pairs():
1649 if name not in ret:
1650 ret[name] = val
1651 elif not isinstance(ret[name], list):
1652 ret[name] = [ret[name], val]
1653 else:
1654 ret[name].append(val)
1655 return ret
15231656
15241657 @property
15251658 def base_url(self):
55 import os
66 import sys
77 from lxml import etree
8 from pyquery.pyquery import PyQuery as pq
8 from pyquery.pyquery import PyQuery as pq, no_default
99 from webtest import http
1010 from webtest.debugapp import debug_app
1111 from .compat import PY3k
160160 self.assertEqual(isinstance(doc.root, etree._ElementTree), True)
161161 self.assertEqual(doc.encoding, 'UTF-8')
162162
163 child = doc.children().eq(0)
164 self.assertNotEqual(child._parent, no_default)
165 self.assertTrue(isinstance(child.root, etree._ElementTree))
166
163167 def test_selector_from_doc(self):
164168 doc = etree.fromstring(self.html)
165169 assert len(self.klass(doc)) == 1
381385 <input name="eggs" value="Eggs">
382386 <input type="checkbox" value="Bacon">
383387 <input type="radio" value="Ham">
388 '''
389
390 html2_newline = '''
391 <input id="newline-text" type="text" name="order" value="S
392 pam">
393 <input id="newline-radio" type="radio" name="order" value="S
394 pam">
384395 '''
385396
386397 html3 = '''
469480 self.assertEqual(d('input[name="eggs"]').val(), '43')
470481 self.assertEqual(d('input:checkbox').val(), '44')
471482 self.assertEqual(d('input:radio').val(), '45')
483
484 def test_val_for_inputs_with_newline(self):
485 d = pq(self.html2_newline)
486 self.assertEqual(d('#newline-text').val(), 'Spam')
487 self.assertEqual(d('#newline-radio').val(), 'S\npam')
472488
473489 def test_val_for_textarea(self):
474490 d = pq(self.html3)
572588 self.assertIn(replacement, new_html)
573589
574590
591 class TestAjax(TestCase):
592
593 html = '''
594 <div id="div">
595 <input form="dispersed" name="order" value="spam">
596 </div>
597 <form id="dispersed">
598 <div><input name="order" value="eggs"></div>
599 <input form="dispersed" name="order" value="ham">
600 <input form="other-form" name="order" value="nothing">
601 <input form="" name="order" value="nothing">
602 </form>
603 <form id="other-form">
604 <input form="dispersed" name="order" value="tomato">
605 </form>
606 <form class="no-id">
607 <input form="dispersed" name="order" value="baked beans">
608 <input name="spam" value="Spam">
609 </form>
610 '''
611
612 html2 = '''
613 <form id="first">
614 <input name="order" value="spam">
615 <fieldset>
616 <input name="fieldset" value="eggs">
617 <input id="input" name="fieldset" value="ham">
618 </fieldset>
619 </form>
620 <form id="datalist">
621 <datalist><div><input name="datalist" value="eggs"></div></datalist>
622 <input type="checkbox" name="checkbox" checked>
623 <input type="radio" name="radio" checked>
624 </form>
625 '''
626
627 html3 = '''
628 <form>
629 <input name="order" value="spam">
630 <input id="noname" value="sausage">
631 <fieldset disabled>
632 <input name="order" value="sausage">
633 </fieldset>
634 <input name="disabled" value="ham" disabled>
635 <input type="submit" name="submit" value="Submit">
636 <input type="button" name="button" value="">
637 <input type="image" name="image" value="">
638 <input type="reset" name="reset" value="Reset">
639 <input type="file" name="file" value="">
640 <button type="submit" name="submit" value="submit"></button>
641 <input type="checkbox" name="spam">
642 <input type="radio" name="eggs">
643 </form>
644 '''
645
646 html4 = '''
647 <form>
648 <input name="spam" value="Spam/
649 spam">
650 <select name="order" multiple>
651 <option value="baked
652 beans" selected>
653 <option value="tomato" selected>
654 <option value="spam">
655 </select>
656 <textarea name="multiline">multiple
657 lines
658 of text</textarea>
659 </form>
660 '''
661
662 def test_serialize_pairs_form_id(self):
663 d = pq(self.html)
664 self.assertEqual(d('#div').serialize_pairs(), [])
665 self.assertEqual(d('#dispersed').serialize_pairs(), [
666 ('order', 'spam'), ('order', 'eggs'), ('order', 'ham'),
667 ('order', 'tomato'), ('order', 'baked beans'),
668 ])
669 self.assertEqual(d('.no-id').serialize_pairs(), [
670 ('spam', 'Spam'),
671 ])
672
673 def test_serialize_pairs_form_controls(self):
674 d = pq(self.html2)
675 self.assertEqual(d('fieldset').serialize_pairs(), [
676 ('fieldset', 'eggs'), ('fieldset', 'ham'),
677 ])
678 self.assertEqual(d('#input, fieldset, #first').serialize_pairs(), [
679 ('order', 'spam'), ('fieldset', 'eggs'), ('fieldset', 'ham'),
680 ('fieldset', 'eggs'), ('fieldset', 'ham'), ('fieldset', 'ham'),
681 ])
682 self.assertEqual(d('#datalist').serialize_pairs(), [
683 ('datalist', 'eggs'), ('checkbox', 'on'), ('radio', 'on'),
684 ])
685
686 def test_serialize_pairs_filter_controls(self):
687 d = pq(self.html3)
688 self.assertEqual(d('form').serialize_pairs(), [
689 ('order', 'spam')
690 ])
691
692 def test_serialize_pairs_form_values(self):
693 d = pq(self.html4)
694 self.assertEqual(d('form').serialize_pairs(), [
695 ('spam', 'Spam/spam'), ('order', 'baked\r\nbeans'),
696 ('order', 'tomato'), ('multiline', 'multiple\r\nlines\r\nof text'),
697 ])
698
699 def test_serialize_array(self):
700 d = pq(self.html4)
701 self.assertEqual(d('form').serialize_array(), [
702 {'name': 'spam', 'value': 'Spam/spam'},
703 {'name': 'order', 'value': 'baked\r\nbeans'},
704 {'name': 'order', 'value': 'tomato'},
705 {'name': 'multiline', 'value': 'multiple\r\nlines\r\nof text'},
706 ])
707
708 def test_serialize(self):
709 d = pq(self.html4)
710 self.assertEqual(
711 d('form').serialize(),
712 'spam=Spam%2Fspam&order=baked%0D%0Abeans&order=tomato&'
713 'multiline=multiple%0D%0Alines%0D%0Aof%20text'
714 )
715
716 def test_serialize_dict(self):
717 d = pq(self.html4)
718 self.assertEqual(d('form').serialize_dict(), {
719 'spam': 'Spam/spam',
720 'order': ['baked\r\nbeans', 'tomato'],
721 'multiline': 'multiple\r\nlines\r\nof text',
722 })
723
724
575725 class TestMakeLinks(TestCase):
576726
577727 html = '''