import unittest
from StringIO import StringIO
import re
import sys

_SIMPLE_XML = r"""<?xml version="1.0"?>
<root xmlns:meld="http://www.plope.com/software/meld3">
  <list meld:id="list">
    <item meld:id="item">
       <name meld:id="name">Name</name>
       <description meld:id="description">Description</description>
    </item>
  </list>
</root>"""

_SIMPLE_XHTML = r"""<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:meld="http://www.plope.com/software/meld3">
   <body meld:id="body">Hello!</body>
</html>"""

_EMPTYTAGS_HTML = """<html>
  <body>
    <area/><base/><basefont/><br/><col/><frame/><hr/><img/><input/><isindex/>
    <link/><meta/><param/>
  </body>
</html>"""

_BOOLEANATTRS_XHTML= """<html>
  <body>
  <tag selected="true"/>
  <tag checked="true"/>
  <tag compact="true"/>
  <tag declare="true"/>
  <tag defer="true"/>
  <tag disabled="true"/>
  <tag ismap="true"/>
  <tag multiple="true"/>
  <tag nohref="true"/>
  <tag noresize="true"/>
  <tag noshade="true"/>
  <tag nowrap="true"/>
  </body>
</html>"""

_ENTITIES_XHTML= r"""
<html>
<head></head>
<body>
  <!-- test entity references -->
  <p>&nbsp;</p>
</body>
</html>"""

_COMPLEX_XHTML = r"""<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:meld="http://www.plope.com/software/meld3"
      xmlns:bar="http://foo/bar">
  <head>
    <meta content="text/html; charset=ISO-8859-1" http-equiv="content-type" />
    <title meld:id="title">This will be escaped in html output: &amp;</title>
    <script>this won't be escaped in html output: &amp;</script>
    <script type="text/javascript">
            //<![CDATA[
              // this won't be escaped in html output
              function match(a,b) {
                 if (a < b && a > 0) then { return 1 }
                }
             //]]>
    </script>
    <style>this won't be escaped in html output: &amp;</style>
  </head>
  <!-- a comment -->
  <body>
    <div bar:baz="slab"/>
    <div meld:id="content_well">
      <form meld:id="form1" action="." method="POST">
      <img src="foo.gif"/>
      <table border="0" meld:id="table1">
        <tbody meld:id="tbody">
          <tr meld:id="tr" class="foo">
            <td meld:id="td1">Name</td>
            <td meld:id="td2">Description</td>
          </tr>
        </tbody>
      </table>
      <input type="submit" name="submit" value=" Next "/>
      </form>
    </div>
  </body>
</html>"""

_NVU_HTML = """<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
  <meta content="text/html; charset=ISO-8859-1" http-equiv="content-type">
  <title meld:id="title">test doc</title>
</head>
<body>
 <!-- comment! -->
 Oh yeah...<br>
<br>
<table style="text-align: left; width: 100px;" border="1" cellpadding="2" cellspacing="2">
  <tbody>
    <tr>
      <td>Yup</td>
      <td>More </td>
      <td>Stuff</td>
      <td>Oh Yeah</td>
    </tr>
    <tr>
      <td>1</td>
      <td>2</td>
      <td>3</td>
      <td>4</td>
    </tr>
    <tr>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
    </tr>
  </tbody>
</table>
<br>
<a href=".">And an image...</a><br>
<br>
<img style="width: 2048px; height: 1536px;" alt="dumb" src="IMG_0102.jpg">
</body>
</html>"""

_FILLMELDFORM_HTML = """\
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
 <title>Emergency Contacts</title>
</head>
<body>
   <div class="header">Emergency Contacts</div>

   <form action="." method="POST">
    <table>

      <tbody meld:id="tbody">

	<tr>
	  <th>Title</th>
	  <td>
	    <input type="text" name="honorific" size="6" value=""
		   meld:id="honorific"/>
	  </td>
	</tr>

	<tr>
	  <th>First Name</th>
	  <td>
	    <input type="text" name="firstname" size="20" value=""
		   meld:id="firstname"/>
	  </td>
	</tr>

	<tr>
	  <th>Middle Name</th>
	  <td>
	    <input type="text" name="middlename" size="15" value=""
		   meld:id="middlename"/>
	  </td>
	</tr>

	<tr>
	  <th>Last Name</th>
	  <td>
	    <input type="text" name="lastname" size="20" value=""
		   meld:id="lastname"/>
	  </td>
	</tr>

	<tr>

	  <th>Suffix</th>
	  <td style="width: 554px;">
          <select name="suffix" meld:id="suffix">
	      <option value="Jr.">Jr.</option>
	      <option value="Sr.">Sr.</option>
	      <option value="III">III</option>
	    </select>
          </td>

        </tr>

	<tr>
	  <th>Address 1</th>
	  <td>
	    <input type="text" name="address1" size="30" value=""
		   meld:id="address1"/>
	  </td>
	</tr>

	<tr>
	  <th>Address 2</th>
	  <td>
	    <input type="text" name="address2" size="30" value=""
		   meld:id="address2"/>
	  </td>
	</tr>

	<tr>
	  <th>City</th>
	  <td>
	    <input type="text" name="city" size="20" value=""
		   meld:id="city"/>
	  </td>
	</tr>

	<tr>
	  <th>State</th>
	  <td>
	    <input type="text" name="state" size="5" value=""
		   meld:id="state"/>
	  </td>
	</tr>

	<tr>
	  <th>ZIP</th>
	  <td>
	    <input type="text" name="zip" size="8" value=""
		   meld:id="zip"/>
	  </td>
	</tr>

	<tr>
	  <th>Home Phone</th>
	  <td>
	    <input type="text" name="homephone" size="12" value=""
		   meld:id="homephone"/>
	  </td>
	</tr>

	<tr>
	  <th>Cell/Mobile Phone</th>
	  <td>
	    <input type="text" name="cellphone" size="12" value=""
		   meld:id="cellphone"/>
	  </td>
	</tr>

	<tr>
	  <th>Email Address</th>
	  <td>
	    <input type="text" name="email" size="20" value=""
		   meld:id="email"/>
	  </td>
	</tr>

	<tr>
	  <th>Over 18? (Checkbox Boolean)</th>
	  <td>
            <input type="checkbox" name="over18" meld:id="over18"
                   value="true" checked="true"/>
	  </td>
	</tr>

	<tr>

	  <th>Mail OK? (Checkbox Ternary)</th>
          <td style="width: 554px;" meld:id="mailok:inputgroup">
            <input type="hidden" name="mailok:default"
                   value="false"/>
            <input type="checkbox" name="mailok"
                   value="true" checked/>
            <input type="checkbox" name="mailok"
                   value="false"/>
          </td>

        </tr>

	<tr>

	  <th>Favorite Color (Radio)</th>
          <td style="width: 554px;" meld:id="favorite_color:inputgroup">
            Red   <input type="radio" name="favorite_color"
                         value="Red"/>
            Green <input type="radio" name="favorite_color"
                         value="Green"/>
            Blue  <input type="radio" name="favorite_color"
                         value="Blue"/>
          </td>

        </tr>

	<tr>
	  <th></th>
	  <td>
	    <input type="submit" value=" Update " name="edit:method" />
	  </td>
	</tr>

      </tbody>
    </table>
    </form>

<p><a href="..">Return to list</a></p>
</body>
</html>
"""

class MeldAPITests(unittest.TestCase):
    def _makeElement(self, string):
        from meld3 import parse_xmlstring
        return parse_xmlstring(string)

    def _makeElementFromHTML(self, string):
        from meld3 import parse_htmlstring
        return parse_htmlstring(string)

    def test_findmeld(self):
        root = self._makeElement(_SIMPLE_XML)
        item = root.findmeld('item')
        self.assertEqual(item.tag, 'item')
        name = root.findmeld('name')
        self.assertEqual(name.text, 'Name')

    def test_findmeld_default(self):
        root = self._makeElement(_SIMPLE_XML)
        item = root.findmeld('item')
        self.assertEqual(item.tag, 'item')
        unknown = root.findmeld('unknown', 'foo')
        self.assertEqual(unknown, 'foo')
        self.assertEqual(root.findmeld('unknown'), None)

    def test_repeat_nochild(self):
        root = self._makeElement(_SIMPLE_XML)
        item = root.findmeld('item')
        self.assertEqual(item.tag, 'item')
        data = [{'name':'Jeff Buckley', 'description':'ethereal'},
                {'name':'Slipknot', 'description':'heavy'}]
        for element, d in item.repeat(data):
            element.findmeld('name').text = d['name']
            element.findmeld('description').text = d['description']
        self.assertEqual(item[0].text, 'Jeff Buckley')
        self.assertEqual(item[1].text, 'ethereal')

    def test_repeat_child(self):
        root = self._makeElement(_SIMPLE_XML)
        list = root.findmeld('list')
        self.assertEqual(list.tag, 'list')
        data = [{'name':'Jeff Buckley', 'description':'ethereal'},
                {'name':'Slipknot', 'description':'heavy'}]
        for element, d in list.repeat(data, 'item'):
            element.findmeld('name').text = d['name']
            element.findmeld('description').text = d['description']
        self.assertEqual(list[0][0].text, 'Jeff Buckley')
        self.assertEqual(list[0][1].text, 'ethereal')
        self.assertEqual(list[1][0].text, 'Slipknot')
        self.assertEqual(list[1][1].text, 'heavy')

    def test_mod(self):
        root = self._makeElement(_SIMPLE_XML)
        root % {'description':'foo', 'name':'bar'}
        name = root.findmeld('name')
        self.assertEqual(name.text, 'bar')
        desc = root.findmeld('description')
        self.assertEqual(desc.text, 'foo')

    def test_fillmelds(self):
        root = self._makeElement(_SIMPLE_XML)
        unfilled = root.fillmelds(**{'description':'foo', 'jammyjam':'a'})
        desc = root.findmeld('description')
        self.assertEqual(desc.text, 'foo')
        self.assertEqual(unfilled, ['jammyjam'])

    def test_fillmeldhtmlform(self):
        data = [
            {'honorific':'Mr.', 'firstname':'Chris', 'middlename':'Phillips',
             'lastname':'McDonough', 'address1':'802 Caroline St.',
             'address2':'Apt. 2B', 'city':'Fredericksburg', 'state': 'VA',
             'zip':'22401', 'homephone':'555-1212', 'cellphone':'555-1313',
             'email':'chrism@plope.com', 'suffix':'Sr.', 'over18':True,
             'mailok:inputgroup':'true', 'favorite_color:inputgroup':'Green'},
            {'honorific':'Mr.', 'firstname':'Fred', 'middlename':'',
             'lastname':'Rogers', 'address1':'1 Imaginary Lane',
             'address2':'Apt. 3A', 'city':'Never Never Land', 'state': 'LA',
             'zip':'00001', 'homephone':'555-1111', 'cellphone':'555-4444',
             'email':'fred@neighborhood.com', 'suffix':'Jr.', 'over18':False,
             'mailok:inputgroup':'false','favorite_color:inputgroup':'Yellow',},
            {'firstname':'Fred', 'middlename':'',
             'lastname':'Rogers', 'address1':'1 Imaginary Lane',
             'address2':'Apt. 3A', 'city':'Never Never Land', 'state': 'LA',
             'zip':'00001', 'homephone':'555-1111', 'cellphone':'555-4444',
             'email':'fred@neighborhood.com', 'suffix':'IV', 'over18':False,
             'mailok:inputgroup':'false', 'favorite_color:inputgroup':'Blue',
             'notthere':1,},
            ]
        root = self._makeElementFromHTML(_FILLMELDFORM_HTML)

        clone = root.clone()
        unfilled = clone.fillmeldhtmlform(**data[0])
        self.assertEqual(unfilled, [])
        self.assertEqual(clone.findmeld('honorific').attrib['value'], 'Mr.')
        self.assertEqual(clone.findmeld('firstname').attrib['value'], 'Chris')
        middlename = clone.findmeld('middlename')
        self.assertEqual(middlename.attrib['value'], 'Phillips')
        suffix = clone.findmeld('suffix')
        self.assertEqual(suffix[1].attrib['selected'], 'selected')
        self.assertEqual(clone.findmeld('over18').attrib['checked'], 'checked')
        mailok = clone.findmeld('mailok:inputgroup')
        self.assertEqual(mailok[1].attrib['checked'], 'checked')
        favoritecolor = clone.findmeld('favorite_color:inputgroup')
        self.assertEqual(favoritecolor[1].attrib['checked'], 'checked')

        clone = root.clone()
        unfilled = clone.fillmeldhtmlform(**data[1])
        self.assertEqual(unfilled, ['favorite_color:inputgroup'])
        self.assertEqual(clone.findmeld('over18').attrib.get('checked'), None)
        mailok = clone.findmeld('mailok:inputgroup')
        self.assertEqual(mailok[2].attrib['checked'], 'checked')
        self.assertEqual(mailok[1].attrib.get('checked'), None)

        clone = root.clone()
        unfilled = clone.fillmeldhtmlform(**data[2])
        self.assertEqual(unfilled, ['notthere', 'suffix'])
        self.assertEqual(clone.findmeld('honorific').text, None)
        favoritecolor = clone.findmeld('favorite_color:inputgroup')
        self.assertEqual(favoritecolor[2].attrib['checked'], 'checked')
        self.assertEqual(favoritecolor[1].attrib.get('checked'), None)

    def test_replace_removes_all_elements(self):
        from meld3 import Replace
        root = self._makeElement(_SIMPLE_XML)
        L = root.findmeld('list')
        L.replace('this is a textual replacement')
        R = root[0]
        self.assertEqual(R.tag, Replace)
        self.assertEqual(len(root.getchildren()), 1)

    def test_replace_replaces_the_right_element(self):
        from meld3 import Replace
        root = self._makeElement(_SIMPLE_XML)
        D = root.findmeld('description')
        D.replace('this is a textual replacement')
        self.assertEqual(len(root.getchildren()), 1)
        L = root[0]
        self.assertEqual(L.tag, 'list')
        self.assertEqual(len(L.getchildren()), 1)
        I = L[0]
        self.assertEqual(I.tag, 'item')
        self.assertEqual(len(I.getchildren()), 2)
        N = I[0]
        self.assertEqual(N.tag, 'name')
        self.assertEqual(len(N.getchildren()), 0)
        D = I[1]
        self.assertEqual(D.tag, Replace)
        self.assertEqual(D.text, 'this is a textual replacement')
        self.assertEqual(len(D.getchildren()), 0)
        self.assertEqual(D.structure, False)

    def test_content(self):
        from meld3 import Replace
        root = self._makeElement(_SIMPLE_XML)
        D = root.findmeld('description')
        D.content('this is a textual replacement')
        self.assertEqual(len(root.getchildren()), 1)
        L = root[0]
        self.assertEqual(L.tag, 'list')
        self.assertEqual(len(L.getchildren()), 1)
        I = L[0]
        self.assertEqual(I.tag, 'item')
        self.assertEqual(len(I.getchildren()), 2)
        N = I[0]
        self.assertEqual(N.tag, 'name')
        self.assertEqual(len(N.getchildren()), 0)
        D = I[1]
        self.assertEqual(D.tag, 'description')
        self.assertEqual(D.text, None)
        self.assertEqual(len(D.getchildren()), 1)
        T = D[0]
        self.assertEqual(T.tag, Replace)
        self.assertEqual(T.text, 'this is a textual replacement')
        self.assertEqual(T.structure, False)

    def test_attributes(self):
        from meld3 import _MELD_ID
        root = self._makeElement(_COMPLEX_XHTML)
        D = root.findmeld('form1')
        D.attributes(foo='bar', baz='1', g='2', action='#')
        self.assertEqual(D.attrib, {
            'foo':'bar', 'baz':'1', 'g':'2',
            'method':'POST', 'action':'#',
            _MELD_ID: 'form1'})

    def test_attributes_nonstringtype_raises(self):
        root = self._makeElement('<root></root>')
        self.assertRaises(ValueError, root.attributes, foo=1)

class MeldElementInterfaceTests(unittest.TestCase):
    def _getTargetClass(self):
        from meld3 import _MeldElementInterface
        return _MeldElementInterface

    def _makeOne(self, *arg, **kw):
        klass = self._getTargetClass()
        return klass(*arg, **kw)

    def test_repeat(self):
        root = self._makeOne('root', {})
        from meld3 import _MELD_ID
        item = self._makeOne('item', {_MELD_ID:'item'})
        record = self._makeOne('record', {_MELD_ID:'record'})
        name = self._makeOne('name', {_MELD_ID:'name'})
        description = self._makeOne('description', {_MELD_ID:'description'})
        record.append(name)
        record.append(description)
        item.append(record)
        root.append(item)

        data = [{'name':'Jeff Buckley', 'description':'ethereal'},
                {'name':'Slipknot', 'description':'heavy'}]

        for element, d in item.repeat(data):
            element.findmeld('name').text = d['name']
            element.findmeld('description').text = d['description']
        self.assertEqual(len(root), 2)
        item1 = root[0]
        self.assertEqual(len(item1), 1)

        record1 = item1[0]
        self.assertEqual(len(record1), 2)

        name1 = record1[0]
        desc1 = record1[1]
        self.assertEqual(name1.text, 'Jeff Buckley')
        self.assertEqual(desc1.text, 'ethereal')

        item2 = root[1]
        self.assertEqual(len(item2), 1)
        record2 = item2[0]
        self.assertEqual(len(record2), 2)
        name2 = record2[0]
        desc2 = record2[1]
        self.assertEqual(name2.text, 'Slipknot')
        self.assertEqual(desc2.text, 'heavy')

    def test_content_simple_nostructure(self):
        el = self._makeOne('div', {'id':'thediv'})
        el.content('hello')
        self.assertEqual(len(el._children), 1)
        replacenode = el._children[0]
        self.assertEqual(replacenode.parent, el)
        self.assertEqual(replacenode.text, 'hello')
        self.assertEqual(replacenode.structure, False)
        from meld3 import Replace
        self.assertEqual(replacenode.tag, Replace)

    def test_content_simple_structure(self):
        el = self._makeOne('div', {'id':'thediv'})
        el.content('hello', structure=True)
        self.assertEqual(len(el._children), 1)
        replacenode = el._children[0]
        self.assertEqual(replacenode.parent, el)
        self.assertEqual(replacenode.text, 'hello')
        self.assertEqual(replacenode.structure, True)
        from meld3 import Replace
        self.assertEqual(replacenode.tag, Replace)

    def test_findmeld_simple(self):
        from meld3 import _MELD_ID
        el = self._makeOne('div', {_MELD_ID:'thediv'})
        self.assertEqual(el.findmeld('thediv'), el)

    def test_findmeld_simple_oneleveldown(self):
        from meld3 import _MELD_ID
        el = self._makeOne('div', {_MELD_ID:'thediv'})
        span = self._makeOne('span', {_MELD_ID:'thespan'})
        el.append(span)
        self.assertEqual(el.findmeld('thespan'), span)

    def test_findmeld_simple_twolevelsdown(self):
        from meld3 import _MELD_ID
        el = self._makeOne('div', {_MELD_ID:'thediv'})
        span = self._makeOne('span', {_MELD_ID:'thespan'})
        a = self._makeOne('a', {_MELD_ID:'thea'})
        span.append(a)
        el.append(span)
        self.assertEqual(el.findmeld('thea'), a)

    def test_ctor(self):
        iface = self._makeOne('div', {'id':'thediv'})
        self.assertEqual(iface.parent, None)
        self.assertEqual(iface.tag, 'div')
        self.assertEqual(iface.attrib, {'id':'thediv'})

    def test_getiterator_simple(self):
        div = self._makeOne('div', {'id':'thediv'})
        iterator = div.getiterator()
        self.assertEqual(len(iterator), 1)
        self.assertEqual(iterator[0], div)

    def test_getiterator(self):
        div = self._makeOne('div', {'id':'thediv'})
        span = self._makeOne('span', {})
        span2 = self._makeOne('span', {'id':'2'})
        span3 = self._makeOne('span3', {'id':'3'})
        span3.text = 'abc'
        span3.tail = '  '
        div.append(span)
        span.append(span2)
        span2.append(span3)

        it = div.getiterator()
        self.assertEqual(len(it), 4)
        self.assertEqual(it[0], div)
        self.assertEqual(it[1], span)
        self.assertEqual(it[2], span2)
        self.assertEqual(it[3], span3)

    def test_getiterator_tag_ignored(self):
        div = self._makeOne('div', {'id':'thediv'})
        span = self._makeOne('span', {})
        span2 = self._makeOne('span', {'id':'2'})
        span3 = self._makeOne('span3', {'id':'3'})
        span3.text = 'abc'
        span3.tail = '  '
        div.append(span)
        span.append(span2)
        span2.append(span3)

        it = div.getiterator(tag='div')
        self.assertEqual(len(it), 4)
        self.assertEqual(it[0], div)
        self.assertEqual(it[1], span)
        self.assertEqual(it[2], span2)
        self.assertEqual(it[3], span3)

    def test_append(self):
        div = self._makeOne('div', {'id':'thediv'})
        span = self._makeOne('span', {})
        div.append(span)
        self.assertEqual(div[0].tag, 'span')
        self.assertEqual(span.parent, div)

    def test__setitem__(self):
        div = self._makeOne('div', {'id':'thediv'})
        span = self._makeOne('span', {})
        span2 = self._makeOne('span', {'id':'2'})
        div.append(span)
        div[0] = span2
        self.assertEqual(div[0].tag, 'span')
        self.assertEqual(div[0].attrib, {'id':'2'})
        self.assertEqual(div[0].parent, div)

    def test_insert(self):
        div = self._makeOne('div', {'id':'thediv'})
        span = self._makeOne('span', {})
        span2 = self._makeOne('span', {'id':'2'})
        div.append(span)
        div.insert(0, span2)
        self.assertEqual(div[0].tag, 'span')
        self.assertEqual(div[0].attrib, {'id':'2'})
        self.assertEqual(div[0].parent, div)
        self.assertEqual(div[1].tag, 'span')
        self.assertEqual(div[1].attrib, {})
        self.assertEqual(div[1].parent, div)

    def test_clone_simple(self):
        div = self._makeOne('div', {'id':'thediv'})
        div.text = 'abc'
        div.tail = '   '
        span = self._makeOne('span', {})
        div.append(span)
        div2 = div.clone()

    def test_clone(self):
        div = self._makeOne('div', {'id':'thediv'})
        span = self._makeOne('span', {})
        span2 = self._makeOne('span', {'id':'2'})
        span3 = self._makeOne('span3', {'id':'3'})
        span3.text = 'abc'
        span3.tail = '  '
        div.append(span)
        span.append(span2)
        span2.append(span3)

        div2 = div.clone()
        self.assertEqual(div.tag, div2.tag)
        self.assertEqual(div.attrib, div2.attrib)
        self.assertEqual(div[0].tag, div2[0].tag)
        self.assertEqual(div[0].attrib, div2[0].attrib)
        self.assertEqual(div[0][0].tag, div2[0][0].tag)
        self.assertEqual(div[0][0].attrib, div2[0][0].attrib)
        self.assertEqual(div[0][0][0].tag, div2[0][0][0].tag)
        self.assertEqual(div[0][0][0].attrib, div2[0][0][0].attrib)
        self.assertEqual(div[0][0][0].text, div2[0][0][0].text)
        self.assertEqual(div[0][0][0].tail, div2[0][0][0].tail)

        self.failIfEqual(id(div), id(div2))
        self.failIfEqual(id(div[0]), id(div2[0]))
        self.failIfEqual(id(div[0][0]), id(div2[0][0]))
        self.failIfEqual(id(div[0][0][0]), id(div2[0][0][0]))

    def test_deparent_noparent(self):
        div = self._makeOne('div', {})
        self.assertEqual(div.parent, None)
        div.deparent()
        self.assertEqual(div.parent, None)

    def test_deparent_withparent(self):
        parent = self._makeOne('parent', {})
        self.assertEqual(parent.parent, None)
        child = self._makeOne('child', {})
        parent.append(child)
        self.assertEqual(parent.parent, None)
        self.assertEqual(child.parent, parent)
        self.assertEqual(parent[0], child)
        child.deparent()
        self.assertEqual(child.parent, None)
        self.assertRaises(IndexError, parent.__getitem__, 0)

    def test_setslice(self):
        parent = self._makeOne('parent', {})
        child1 = self._makeOne('child1', {})
        child2 = self._makeOne('child2', {})
        child3 = self._makeOne('child3', {})
        children = (child1, child2, child3)
        parent[0:2] = children
        self.assertEqual(child1.parent, parent)
        self.assertEqual(child2.parent, parent)
        self.assertEqual(child3.parent, parent)
        self.assertEqual(parent._children, list(children))

    def test_delslice(self):
        parent = self._makeOne('parent', {})
        child1 = self._makeOne('child1', {})
        child2 = self._makeOne('child2', {})
        child3 = self._makeOne('child3', {})
        children = (child1, child2, child3)
        parent[0:2] = children
        del parent[0:2]
        self.assertEqual(child1.parent, None)
        self.assertEqual(child2.parent, None)
        self.assertEqual(child3.parent, parent)
        self.assertEqual(len(parent._children), 1)

    def test_remove(self):
        parent = self._makeOne('parent', {})
        child1 = self._makeOne('child1', {})
        parent.append(child1)
        parent.remove(child1)
        self.assertEqual(child1.parent, None)
        self.assertEqual(len(parent._children), 0)

    def test_lineage(self):
        from meld3 import _MELD_ID
        div1 = self._makeOne('div', {_MELD_ID:'div1'})
        span1 = self._makeOne('span', {_MELD_ID:'span1'})
        span2 = self._makeOne('span', {_MELD_ID:'span2'})
        span3 = self._makeOne('span', {_MELD_ID:'span3'})
        span4 = self._makeOne('span', {_MELD_ID:'span4'})
        span5 = self._makeOne('span', {_MELD_ID:'span5'})
        span6 = self._makeOne('span', {_MELD_ID:'span6'})
        unknown = self._makeOne('span', {})
        div2 = self._makeOne('div2', {_MELD_ID:'div2'})
        div1.append(span1)
        span1.append(span2)
        span2.append(span3)
        span3.append(unknown)
        unknown.append(span4)
        span4.append(span5)
        span5.append(span6)
        div1.append(div2)
        def ids(L):
            return [ x.meldid() for x in L ]
        self.assertEqual(ids(div1.lineage()), ['div1'])
        self.assertEqual(ids(span1.lineage()), ['span1', 'div1'])
        self.assertEqual(ids(span2.lineage()), ['span2', 'span1', 'div1'])
        self.assertEqual(ids(span3.lineage()), ['span3', 'span2', 'span1',
                                                    'div1'])
        self.assertEqual(ids(unknown.lineage()), [None, 'span3', 'span2',
                                                  'span1',
                                                  'div1'])
        self.assertEqual(ids(span4.lineage()), ['span4', None, 'span3',
                                                'span2',
                                                'span1','div1'])

        self.assertEqual(ids(span5.lineage()), ['span5', 'span4', None,
                                                'span3', 'span2',
                                                'span1','div1'])
        self.assertEqual(ids(span6.lineage()), ['span6', 'span5', 'span4',
                                                None,'span3', 'span2',
                                                    'span1','div1'])
        self.assertEqual(ids(div2.lineage()), ['div2', 'div1'])


    def test_shortrepr(self):
        div = self._makeOne('div', {'id':'div1'})
        span = self._makeOne('span', {})
        span2 = self._makeOne('span', {'id':'2'})
        span3 = self._makeOne('span3', {'id':'3'})
        span4 = self._makeOne('span4', {'id':'4'})
        span5 = self._makeOne('span5', {'id':'5'})
        span6 = self._makeOne('span6', {'id':'6'})
        div2 = self._makeOne('div2', {'id':'div2'})
        div.append(span)
        span.append(span2)
        div.append(div2)
        r = div.shortrepr()
        self.assertEqual(r, '<div id="div1"><span><span id="2"></span></span><div2 id="div2"></div2></div>')

    def test_shortrepr2(self):
        from meld3 import parse_xmlstring
        root = parse_xmlstring(_COMPLEX_XHTML)
        r = root.shortrepr()
        self.assertEqual(r, '<html>\n  <head>\n    <meta content="text/html; charset=ISO-8859-1" http-equiv="content-type">\n     [...]\n</head>\n  <!--  a comment  -->\n   [...]\n</html>')


    def test_diffmeld1(self):
        from meld3 import parse_xmlstring
        from meld3 import _MELD_ID
        root = parse_xmlstring(_COMPLEX_XHTML)
        clone = root.clone()
        div = self._makeOne('div', {_MELD_ID:'newdiv'})
        clone.append(div)
        tr = clone.findmeld('tr')
        tr.deparent()
        title = clone.findmeld('title')
        title.deparent()
        clone.append(title)

        # unreduced
        diff = root.diffmeld(clone)
        changes = diff['unreduced']
        addedtags = [ x.attrib[_MELD_ID] for x in changes['added'] ]
        removedtags = [x.attrib[_MELD_ID] for x in changes['removed'] ]
        movedtags = [ x.attrib[_MELD_ID] for x in changes['moved'] ]
        addedtags.sort()
        removedtags.sort()
        movedtags.sort()
        self.assertEqual(addedtags,['newdiv'])
        self.assertEqual(removedtags,['td1', 'td2', 'tr'])
        self.assertEqual(movedtags, ['title'])

        # reduced
        changes = diff['reduced']
        addedtags = [ x.attrib[_MELD_ID] for x in changes['added'] ]
        removedtags = [x.attrib[_MELD_ID] for x in changes['removed'] ]
        movedtags = [ x.attrib[_MELD_ID] for x in changes['moved'] ]
        addedtags.sort()
        removedtags.sort()
        movedtags.sort()
        self.assertEqual(addedtags,['newdiv'])
        self.assertEqual(removedtags,['tr'])
        self.assertEqual(movedtags, ['title'])

    def test_diffmeld2(self):
        source = """
        <root>
          <a meld:id="a">
             <b meld:id="b"></b>
          </a>
        </root>"""
        target = """
        <root>
          <a meld:id="a"></a>
          <b meld:id="b"></b>
        </root>
        """
        from meld3 import parse_htmlstring
        source_root = parse_htmlstring(source)
        target_root = parse_htmlstring(target)
        changes = source_root.diffmeld(target_root)

        # unreduced
        actual = [x.meldid() for x in changes['unreduced']['moved']]
        expected = ['b']
        self.assertEqual(expected, actual)

        actual = [x.meldid() for x in changes['unreduced']['added']]
        expected = []
        self.assertEqual(expected, actual)

        actual = [x.meldid() for x in changes['unreduced']['removed']]
        expected = []
        self.assertEqual(expected, actual)

        # reduced
        actual = [x.meldid() for x in changes['reduced']['moved']]
        expected = ['b']
        self.assertEqual(expected, actual)

        actual = [x.meldid() for x in changes['reduced']['added']]
        expected = []
        self.assertEqual(expected, actual)

        actual = [x.meldid() for x in changes['reduced']['removed']]
        expected = []
        self.assertEqual(expected, actual)


    def test_diffmeld3(self):
        source = """
        <root>
          <a meld:id="a">
             <b meld:id="b">
               <c meld:id="c"></c>
             </b>
          </a>
          <z meld:id="z">
            <y meld:id="y"></y>
          </z>
        </root>
        """
        target = """
        <root>
          <b meld:id="b">
            <c meld:id="c"></c>
          </b>
          <a meld:id="a"></a>
          <d meld:id="d">
             <e meld:id="e"></e>
          </d>
        </root>
        """
        from meld3 import parse_htmlstring
        source_root = parse_htmlstring(source)
        target_root = parse_htmlstring(target)
        changes = source_root.diffmeld(target_root)

        # unreduced
        actual = [x.meldid() for x in changes['unreduced']['moved']]
        expected = ['b', 'c']
        self.assertEqual(expected, actual)

        actual = [x.meldid() for x in changes['unreduced']['added']]
        expected = ['d', 'e']
        self.assertEqual(expected, actual)

        actual = [x.meldid() for x in changes['unreduced']['removed']]
        expected = ['z', 'y']
        self.assertEqual(expected, actual)

        # reduced
        actual = [x.meldid() for x in changes['reduced']['moved']]
        expected = ['b']
        self.assertEqual(expected, actual)

        actual = [x.meldid() for x in changes['reduced']['added']]
        expected = ['d']
        self.assertEqual(expected, actual)

        actual = [x.meldid() for x in changes['reduced']['removed']]
        expected = ['z']
        self.assertEqual(expected, actual)

    def test_diffmeld4(self):
        source = """
        <root>
          <a meld:id="a">
             <b meld:id="b">
               <c meld:id="c">
                 <d meld:id="d"></d>
               </c>
             </b>
          </a>
          <z meld:id="z">
            <y meld:id="y"></y>
          </z>
        </root>
        """
        target = """
        <root>
          <p>
            <a meld:id="a">
               <b meld:id="b"></b>
            </a>
          </p>
          <p>
            <m meld:id="m">
              <n meld:id="n"></n>
            </m>
          </p>
        </root>
        """
        from meld3 import parse_htmlstring
        source_root = parse_htmlstring(source)
        target_root = parse_htmlstring(target)
        changes = source_root.diffmeld(target_root)

        # unreduced
        actual = [x.meldid() for x in changes['unreduced']['moved']]
        expected = ['a', 'b']
        self.assertEqual(expected, actual)

        actual = [x.meldid() for x in changes['unreduced']['added']]
        expected = ['m', 'n']
        self.assertEqual(expected, actual)

        actual = [x.meldid() for x in changes['unreduced']['removed']]
        expected = ['c', 'd', 'z', 'y']
        self.assertEqual(expected, actual)

        # reduced
        actual = [x.meldid() for x in changes['reduced']['moved']]
        expected = ['a']
        self.assertEqual(expected, actual)

        actual = [x.meldid() for x in changes['reduced']['added']]
        expected = ['m']
        self.assertEqual(expected, actual)

        actual = [x.meldid() for x in changes['reduced']['removed']]
        expected = ['c', 'z']
        self.assertEqual(expected, actual)

    def test_diffmeld5(self):
        source = """
        <root>
          <a meld:id="a">
             <b meld:id="b">
               <c meld:id="c">
                 <d meld:id="d"></d>
               </c>
             </b>
          </a>
          <z meld:id="z">
            <y meld:id="y"></y>
          </z>
        </root>
        """
        target = """
        <root>
          <p>
            <a meld:id="a">
               <b meld:id="b">
                 <p>
                   <c meld:id="c">
                     <d meld:id="d"></d>
                   </c>
                 </p>
               </b>
            </a>
          </p>
          <z meld:id="z">
            <y meld:id="y"></y>
          </z>
        </root>
        """
        from meld3 import parse_htmlstring
        source_root = parse_htmlstring(source)
        target_root = parse_htmlstring(target)
        changes = source_root.diffmeld(target_root)

        # unreduced
        actual = [x.meldid() for x in changes['unreduced']['moved']]
        expected = ['a', 'b', 'c', 'd']
        self.assertEqual(expected, actual)

        actual = [x.meldid() for x in changes['unreduced']['added']]
        expected = []
        self.assertEqual(expected, actual)

        actual = [x.meldid() for x in changes['unreduced']['removed']]
        expected = []
        self.assertEqual(expected, actual)

        # reduced
        actual = [x.meldid() for x in changes['reduced']['moved']]
        expected = ['a', 'c']
        self.assertEqual(expected, actual)

        actual = [x.meldid() for x in changes['reduced']['added']]
        expected = []
        self.assertEqual(expected, actual)

        actual = [x.meldid() for x in changes['reduced']['removed']]
        expected = []
        self.assertEqual(expected, actual)



class ParserTests(unittest.TestCase):
    def _parse(self, *args):
        from meld3 import parse_xmlstring
        root = parse_xmlstring(*args)
        return root

    def _parse_html(self, *args):
        from meld3 import parse_htmlstring
        root = parse_htmlstring(*args)
        return root

    def test_parse_simple_xml(self):
        from meld3 import _MELD_ID
        root = self._parse(_SIMPLE_XML)
        self.assertEqual(root.tag, 'root')
        self.assertEqual(root.parent, None)
        l1st = root[0]
        self.assertEqual(l1st.tag, 'list')
        self.assertEqual(l1st.parent, root)
        self.assertEqual(l1st.attrib[_MELD_ID], 'list')
        item = l1st[0]
        self.assertEqual(item.tag, 'item')
        self.assertEqual(item.parent, l1st)
        self.assertEqual(item.attrib[_MELD_ID], 'item')
        name = item[0]
        description = item[1]
        self.assertEqual(name.tag, 'name')
        self.assertEqual(name.parent, item)
        self.assertEqual(name.attrib[_MELD_ID], 'name')
        self.assertEqual(description.tag, 'description')
        self.assertEqual(description.parent, item)
        self.assertEqual(description.attrib[_MELD_ID], 'description')

    def test_parse_simple_xhtml(self):
        xhtml_ns = '{http://www.w3.org/1999/xhtml}%s'
        from meld3 import _MELD_ID

        root = self._parse(_SIMPLE_XHTML)
        self.assertEqual(root.tag, xhtml_ns % 'html')
        self.assertEqual(root.attrib, {})
        self.assertEqual(root.parent, None)
        body = root[0]
        self.assertEqual(body.tag, xhtml_ns % 'body')
        self.assertEqual(body.attrib[_MELD_ID], 'body')
        self.assertEqual(body.parent, root)

    def test_parse_complex_xhtml(self):
        xhtml_ns = '{http://www.w3.org/1999/xhtml}%s'
        from meld3 import _MELD_ID
        root = self._parse(_COMPLEX_XHTML)
        self.assertEqual(root.tag, xhtml_ns % 'html')
        self.assertEqual(root.attrib, {})
        self.assertEqual(root.parent, None)
        head = root[0]
        self.assertEqual(head.tag, xhtml_ns % 'head')
        self.assertEqual(head.attrib, {})
        self.assertEqual(head.parent, root)
        meta = head[0]
        self.assertEqual(meta.tag, xhtml_ns % 'meta')
        self.assertEqual(meta.attrib['content'],
                         'text/html; charset=ISO-8859-1')
        self.assertEqual(meta.parent, head)
        title = head[1]
        self.assertEqual(title.tag, xhtml_ns % 'title')
        self.assertEqual(title.attrib[_MELD_ID], 'title')
        self.assertEqual(title.parent, head)
        comment = root[1]
        body = root[2]
        self.assertEqual(body.tag, xhtml_ns % 'body')
        self.assertEqual(body.attrib, {})
        self.assertEqual(body.parent, root)

        div1 = body[0]
        self.assertEqual(div1.tag, xhtml_ns % 'div')
        self.assertEqual(div1.attrib, {'{http://foo/bar}baz': 'slab'})
        self.assertEqual(div1.parent, body)

        div2 = body[1]
        self.assertEqual(div2.tag, xhtml_ns % 'div')
        self.assertEqual(div2.attrib[_MELD_ID], 'content_well')
        self.assertEqual(div2.parent, body)

        form = div2[0]
        self.assertEqual(form.tag, xhtml_ns % 'form')
        self.assertEqual(form.attrib[_MELD_ID], 'form1')
        self.assertEqual(form.attrib['action'], '.')
        self.assertEqual(form.attrib['method'], 'POST')
        self.assertEqual(form.parent, div2)

        img = form[0]
        self.assertEqual(img.tag, xhtml_ns % 'img')
        self.assertEqual(img.parent, form)

        table = form[1]
        self.assertEqual(table.tag, xhtml_ns % 'table')
        self.assertEqual(table.attrib[_MELD_ID], 'table1')
        self.assertEqual(table.attrib['border'], '0')
        self.assertEqual(table.parent, form)

        tbody = table[0]
        self.assertEqual(tbody.tag, xhtml_ns % 'tbody')
        self.assertEqual(tbody.attrib[_MELD_ID], 'tbody')
        self.assertEqual(tbody.parent, table)

        tr = tbody[0]
        self.assertEqual(tr.tag, xhtml_ns % 'tr')
        self.assertEqual(tr.attrib[_MELD_ID], 'tr')
        self.assertEqual(tr.attrib['class'], 'foo')
        self.assertEqual(tr.parent, tbody)

        td1 = tr[0]
        self.assertEqual(td1.tag, xhtml_ns % 'td')
        self.assertEqual(td1.attrib[_MELD_ID], 'td1')
        self.assertEqual(td1.parent, tr)

        td2 = tr[1]
        self.assertEqual(td2.tag, xhtml_ns % 'td')
        self.assertEqual(td2.attrib[_MELD_ID], 'td2')
        self.assertEqual(td2.parent, tr)

    def test_nvu_html(self):
        from meld3 import _MELD_ID
        from meld3 import Comment
        root = self._parse_html(_NVU_HTML)
        self.assertEqual(root.tag, 'html')
        self.assertEqual(root.attrib, {})
        self.assertEqual(root.parent, None)
        head = root[0]
        self.assertEqual(head.tag, 'head')
        self.assertEqual(head.attrib, {})
        self.assertEqual(head.parent, root)
        meta = head[0]
        self.assertEqual(meta.tag, 'meta')
        self.assertEqual(meta.attrib['content'],
                         'text/html; charset=ISO-8859-1')
        title = head[1]
        self.assertEqual(title.tag, 'title')
        self.assertEqual(title.attrib[_MELD_ID], 'title')
        self.assertEqual(title.parent, head)

        body = root[1]
        self.assertEqual(body.tag, 'body')
        self.assertEqual(body.attrib, {})
        self.assertEqual(body.parent, root)

        comment = body[0]
        self.assertEqual(comment.tag, Comment)

        br1 = body[1]
        br2 = body[2]
        table = body[3]

        self.assertEqual(table.tag, 'table')
        self.assertEqual(table.attrib, {'style':
                                        'text-align: left; width: 100px;',
                                        'border':'1',
                                        'cellpadding':'2',
                                        'cellspacing':'2'})
        self.assertEqual(table.parent, body)
        br3 = body[4]
        href = body[5]
        self.assertEqual(href.tag, 'a')
        br4 = body[6]
        br5 = body[7]
        img = body[8]
        self.assertEqual(img.tag, 'img')


    def test_dupe_meldids_fails_parse_xml(self):
        meld_ns = "http://www.plope.com/software/meld3"
        repeated = ('<html xmlns:meld="%s" meld:id="repeat">'
                    '<body meld:id="repeat"/></html>' % meld_ns)
        self.assertRaises(ValueError, self._parse, repeated)

    def test_dupe_meldids_fails_parse_html(self):
        meld_ns = "http://www.plope.com/software/meld3"
        repeated = ('<html xmlns:meld="%s" meld:id="repeat">'
                    '<body meld:id="repeat"/></html>' % meld_ns)
        self.assertRaises(ValueError, self._parse_html, repeated)

class UtilTests(unittest.TestCase):

    def test_insert_xhtml_doctype(self):
        from meld3 import insert_doctype
        orig = '<root></root>'
        actual = insert_doctype(orig)
        expected = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><root></root>'
        self.assertEqual(actual, expected)

    def test_insert_doctype_after_xmldecl(self):
        from meld3 import insert_doctype
        orig = '<?xml version="1.0" encoding="latin-1"?><root></root>'
        actual = insert_doctype(orig)
        expected = '<?xml version="1.0" encoding="latin-1"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><root></root>'
        self.assertEqual(actual, expected)

    def test_insert_meld_ns_decl(self):
        from meld3 import insert_meld_ns_decl
        orig = '<?xml version="1.0" encoding="latin-1"?><root></root>'
        actual = insert_meld_ns_decl(orig)
        expected = '<?xml version="1.0" encoding="latin-1"?><root xmlns:meld="http://www.plope.com/software/meld3"></root>'
        self.assertEqual(actual, expected)

    def test_prefeed_preserves_existing_meld_ns(self):
        from meld3 import prefeed
        orig = '<?xml version="1.0" encoding="latin-1"?><root xmlns:meld="#"></root>'
        actual = prefeed(orig)
        expected = '<?xml version="1.0" encoding="latin-1"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><root xmlns:meld="#"></root>'
        self.assertEqual(actual, expected)

    def test_prefeed_preserves_existing_doctype(self):
        from meld3 import prefeed
        orig = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><root xmlns:meld="http://www.plope.com/software/meld3"></root>'
        actual = prefeed(orig)
        self.assertEqual(actual, orig)

class WriterTests(unittest.TestCase):
    def _parse(self, xml):
        from meld3 import parse_xmlstring
        root = parse_xmlstring(xml)
        return root

    def _parse_html(self, xml):
        from meld3 import parse_htmlstring
        root = parse_htmlstring(xml)
        return root

    def _write(self, fn, **kw):
        out = StringIO()
        fn(out, **kw)
        out.seek(0)
        actual = out.read()
        return actual

    def _write_xml(self, node, **kw):
        return self._write(node.write_xml, **kw)

    def _write_html(self, node, **kw):
        return self._write(node.write_html, **kw)

    def _write_xhtml(self, node, **kw):
        return self._write(node.write_xhtml, **kw)

    def assertNormalizedXMLEqual(self, a, b):
        a = normalize_xml(a)
        b = normalize_xml(b)
        self.assertEqual(a, b)

    def assertNormalizedHTMLEqual(self, a, b):
        a = normalize_html(a)
        b = normalize_html(b)
        self.assertEqual(a, b)

    def test_write_simple_xml(self):
        root = self._parse(_SIMPLE_XML)
        actual = self._write_xml(root)
        expected = """<?xml version="1.0"?><root>
  <list>
    <item>
       <name>Name</name>
       <description>Description</description>
    </item>
  </list>
</root>"""
        self.assertNormalizedXMLEqual(actual, expected)

        for el, data in root.findmeld('item').repeat(((1,2),)):
            el.findmeld('name').text = str(data[0])
            el.findmeld('description').text = str(data[1])
        actual = self._write_xml(root)
        expected = """<?xml version="1.0"?><root>
  <list>
    <item>
       <name>1</name>
       <description>2</description>
    </item>
  </list>
</root>"""
        self.assertNormalizedXMLEqual(actual, expected)

    def test_write_simple_xhtml(self):
        root = self._parse(_SIMPLE_XHTML)
        actual = self._write_xhtml(root)
        expected = """<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html><body>Hello!</body></html>"""
        self.assertNormalizedXMLEqual(actual, expected)

    def test_write_simple_xhtml_as_html(self):
        root = self._parse(_SIMPLE_XHTML)
        actual = self._write_html(root)
        expected = """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
   <body>Hello!</body>
</html>"""
        self.assertNormalizedHTMLEqual(actual, expected)

    def test_write_complex_xhtml_as_html(self):
        root = self._parse(_COMPLEX_XHTML)
        actual = self._write_html(root)
        expected = """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
  <head>
    <meta content="text/html; charset=ISO-8859-1" http-equiv="content-type">
    <title>This will be escaped in html output: &amp;</title>
    <script>this won't be escaped in html output: &</script>
    <script type="text/javascript">
            //
              // this won't be escaped in html output
              function match(a,b) {
                 if (a < b && a > 0) then { return 1 }
                }
             //
    </script>
    <style>this won't be escaped in html output: &</style>
  </head>
  <!-- a comment -->
  <body>
    <div></div>
    <div>
      <form action="." method="POST">
      <img src="foo.gif">
      <table border="0">
        <tbody>
          <tr class="foo">
            <td>Name</td>
            <td>Description</td>
          </tr>
        </tbody>
      </table>
      <input name="submit" type="submit" value=" Next ">
      </form>
    </div>
  </body>
</html>"""

        self.assertNormalizedHTMLEqual(actual, expected)

    def test_write_complex_xhtml_as_xhtml(self):
        # I'm not entirely sure if the cdata "script" quoting in this
        # test is entirely correct for XHTML.  Ryan Tomayko suggests
        # that escaped entities are handled properly in script tags by
        # XML-aware browsers at
        # http://sourceforge.net/mailarchive/message.php?msg_id=10835582
        # but I haven't tested it at all.  ZPT does not seem to do
        # this; it outputs unescaped data.
        root = self._parse(_COMPLEX_XHTML)
        actual = self._write_xhtml(root)
        expected = """<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
  <head>
    <meta content="text/html; charset=ISO-8859-1" http-equiv="content-type" />
    <title>This will be escaped in html output: &amp;</title>
    <script>this won't be escaped in html output: &amp;</script>
    <script type="text/javascript">
            //
              // this won't be escaped in html output
              function match(a,b) {
                 if (a &lt; b &amp;&amp; a > 0) then { return 1 }
                }
             //
    </script>
    <style>this won't be escaped in html output: &amp;</style>
  </head>
  <!--  a comment  -->
  <body>
    <div ns0:baz="slab" xmlns:ns0="http://foo/bar" />
    <div>
      <form action="." method="POST">
      <img src="foo.gif" />
      <table border="0">
        <tbody>
          <tr class="foo">
            <td>Name</td>
            <td>Description</td>
          </tr>
        </tbody>
      </table>
      <input name="submit" type="submit" value=" Next " />
      </form>
    </div>
  </body>
</html>"""
        self.assertNormalizedXMLEqual(actual, expected)

    def test_write_emptytags_html(self):
        root = self._parse(_EMPTYTAGS_HTML)
        actual = self._write_html(root)
        expected = """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
  <body>
    <area><base><basefont><br><col><frame><hr><img><input><isindex>
    <link><meta><param>
  </body>
</html>"""
        self.assertEqual(actual, expected)

    def test_write_booleanattrs_xhtml_as_html(self):
        root = self._parse(_BOOLEANATTRS_XHTML)
        actual = self._write_html(root)
        expected = """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
  <body>
  <tag selected></tag>
  <tag checked></tag>
  <tag compact></tag>
  <tag declare></tag>
  <tag defer></tag>
  <tag disabled></tag>
  <tag ismap></tag>
  <tag multiple></tag>
  <tag nohref></tag>
  <tag noresize></tag>
  <tag noshade></tag>
  <tag nowrap></tag>
  </body>
</html>"""
        self.assertNormalizedHTMLEqual(actual, expected)

    def test_write_simple_xhtml_pipeline(self):
        root = self._parse(_SIMPLE_XHTML)
        actual = self._write_xhtml(root, pipeline=True)
        expected = """<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html><body ns0:id="body" xmlns:ns0="http://www.plope.com/software/meld3">Hello!</body></html>"""
        self.assertNormalizedXMLEqual(actual, expected)

    def test_write_simple_xml_pipeline(self):
        root = self._parse(_SIMPLE_XML)
        actual = self._write_xml(root, pipeline=True)
        expected = """<?xml version="1.0"?><root>
  <list ns0:id="list" xmlns:ns0="http://www.plope.com/software/meld3">
    <item ns0:id="item">
       <name ns0:id="name">Name</name>
       <description ns0:id="description">Description</description>
    </item>
  </list>
</root>"""
        self.assertNormalizedXMLEqual(actual, expected)

    def test_write_simple_xml_override_encoding(self):
        root = self._parse(_SIMPLE_XML)
        from meld3 import doctype
        actual = self._write_xml(root, encoding="latin-1")
        expected = """<?xml version="1.0" encoding="latin-1"?><root>
  <list>
    <item>
       <name>Name</name>
       <description>Description</description>
    </item>
  </list>
</root>"""
        self.assertNormalizedXMLEqual(actual, expected)


    def test_write_simple_xml_as_fragment(self):
        root = self._parse(_SIMPLE_XML)
        from meld3 import doctype
        actual = self._write_xml(root, fragment=True)
        expected = """<root>
  <list>
    <item>
       <name>Name</name>
       <description>Description</description>
    </item>
  </list>
</root>"""
        self.assertNormalizedXMLEqual(actual, expected)

    def test_write_simple_xml_with_doctype(self):
        root = self._parse(_SIMPLE_XML)
        from meld3 import doctype
        actual = self._write_xml(root, doctype=doctype.xhtml)
        expected = """<?xml version="1.0"?>
        <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><root>
  <list>
    <item>
       <name>Name</name>
       <description>Description</description>
    </item>
  </list>
</root>"""
        self.assertNormalizedXMLEqual(actual, expected)

    def test_write_simple_xml_doctype_nodeclaration(self):
        root = self._parse(_SIMPLE_XML)
        from meld3 import doctype
        actual = self._write_xml(root, declaration=False,
                                 doctype=doctype.xhtml)
        expected = """<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><root>
  <list>
    <item>
       <name>Name</name>
       <description>Description</description>
    </item>
  </list>
</root>"""
        self.assertNormalizedXMLEqual(actual, expected)

    def test_write_simple_xml_fragment_kills_doctype_and_declaration(self):
        root = self._parse(_SIMPLE_XML)
        from meld3 import doctype
        actual = self._write_xml(root, declaration=True,
                                 doctype=doctype.xhtml, fragment=True)
        expected = """<root>
  <list>
    <item>
       <name>Name</name>
       <description>Description</description>
    </item>
  </list>
</root>"""
        self.assertNormalizedXMLEqual(actual, expected)

    def test_write_simple_xhtml_override_encoding(self):
        root = self._parse(_SIMPLE_XHTML)
        from meld3 import doctype
        actual = self._write_xhtml(root, encoding="latin-1", declaration=True)
        expected = """<?xml version="1.0" encoding="latin-1"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html><body>Hello!</body></html>"""
        self.assertNormalizedXMLEqual(actual, expected)

    def test_write_simple_xhtml_as_fragment(self):
        root = self._parse(_SIMPLE_XHTML)
        from meld3 import doctype
        actual = self._write_xhtml(root, fragment=True)
        expected = """<html><body>Hello!</body></html>"""
        self.assertNormalizedXMLEqual(actual, expected)

    def test_write_simple_xhtml_with_doctype(self):
        root = self._parse(_SIMPLE_XHTML)
        from meld3 import doctype
        actual = self._write_xhtml(root, doctype=doctype.xhtml)
        expected = """<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html><body>Hello!</body></html>"""
        self.assertNormalizedXMLEqual(actual, expected)

    def test_write_simple_xhtml_doctype_nodeclaration(self):
        root = self._parse(_SIMPLE_XHTML)
        from meld3 import doctype
        actual = self._write_xhtml(root, declaration=False,
                                 doctype=doctype.xhtml)
        expected = """<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html><body>Hello!</body></html>"""
        self.assertNormalizedXMLEqual(actual, expected)

    def test_write_simple_xhtml_fragment_kills_doctype_and_declaration(self):
        root = self._parse(_SIMPLE_XHTML)
        from meld3 import doctype
        actual = self._write_xhtml(root, declaration=True,
                                 doctype=doctype.xhtml, fragment=True)
        expected = """<html><body>Hello!</body></html>"""
        self.assertNormalizedXMLEqual(actual, expected)

    def test_write_simple_xhtml_as_html_fragment(self):
        root = self._parse(_SIMPLE_XHTML)
        from meld3 import doctype
        actual = self._write_html(root, fragment=True)
        expected = """<html><body>Hello!</body></html>"""
        self.assertNormalizedXMLEqual(actual, expected)

    def test_write_simple_xhtml_with_doctype_as_html(self):
        root = self._parse(_SIMPLE_XHTML)
        actual = self._write_html(root)
        expected = """
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html><body>Hello!</body></html>"""
        self.assertNormalizedXMLEqual(actual, expected)

    def test_write_simple_xhtml_as_html_new_doctype(self):
        root = self._parse(_SIMPLE_XHTML)
        from meld3 import doctype
        actual = self._write_html(root, doctype=doctype.html_strict)
        expected = """
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html><body>Hello!</body></html>"""
        self.assertNormalizedXMLEqual(actual, expected)

    def test_write_entities_xhtml_no_doctype(self):
        root = self._parse_html(_ENTITIES_XHTML)
        # this will be considered an XHTML document by default; we needn't
        # declare a doctype
        actual = self._write_xhtml(root)
        expected =r"""<html>
<head></head>
<body>
  <!-- test entity references -->
  <p>&nbsp;</p>
</body>
</html>"""

    def test_write_entities_xhtml_with_doctype(self):
        dt = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
        root = self._parse_html(dt + _ENTITIES_XHTML)
        actual = self._write_xhtml(root)
        expected =r"""<html>
<head></head>
<body>
  <!-- test entity references -->
  <p>&nbsp;</p>
</body>
</html>"""

    def test_unknown_entity(self):
        # exception thrown may vary by python or expat version
        from xml.parsers import expat
        self.assertRaises((expat.error, SyntaxError), self._parse,
                          '<html><head></head><body>&fleeb;</body></html>')

    def test_content_nostructure(self):
        root = self._parse(_SIMPLE_XML)
        D = root.findmeld('description')
        D.content('description &<foo>&amp;<bar>', structure=False)
        actual = self._write_xml(root)
        expected = """<?xml version="1.0"?>
        <root>
        <list>
        <item>
        <name>Name</name>
          <description>description &amp;&lt;foo>&amp;&lt;bar></description>
        </item>
        </list>
        </root>"""
        self.assertNormalizedXMLEqual(actual, expected)

    def test_content_structure(self):
        root = self._parse(_SIMPLE_XML)
        D = root.findmeld('description')
        D.content('description &<foo> <bar>', structure=True)
        actual = self._write_xml(root)
        expected = """<?xml version="1.0"?>
        <root>
        <list>
        <item>
        <name>Name</name>
          <description>description &<foo> <bar></description>
        </item>
        </list>
        </root>"""
        self.assertNormalizedXMLEqual(actual, expected)

    def test_replace_nostructure(self):
        root = self._parse(_SIMPLE_XML)
        D = root.findmeld('description')
        D.replace('description &<foo>&amp;<bar>', structure=False)
        actual = self._write_xml(root)
        expected = """<?xml version="1.0"?>
        <root>
        <list>
        <item>
        <name>Name</name>
          description &amp;&lt;foo>&amp;&lt;bar>
        </item>
        </list>
        </root>"""
        self.assertNormalizedXMLEqual(actual, expected)

    def test_replace_structure(self):
        root = self._parse(_SIMPLE_XML)
        D = root.findmeld('description')
        D.replace('description &<foo> <bar>', structure=True)
        actual = self._write_xml(root)
        expected = """<?xml version="1.0"?>
        <root>
        <list>
        <item>
        <name>Name</name>
          description &<foo> <bar>
        </item>
        </list>
        </root>"""
        self.assertNormalizedXMLEqual(actual, expected)

    def test_escape_cdata(self):
        a = '< > &lt;&amp; &&apos; && &foo "" http://www.plope.com?foo=bar&bang=baz &#123;'
        from meld3 import _escape_cdata
        self.assertEqual('&lt; > &lt;&amp; &amp;&apos; &amp;&amp; &amp;foo "" http://www.plope.com?foo=bar&amp;bang=baz &#123;', _escape_cdata(a))

    def test_escape_cdata_unicodeerror(self):
        a = eval(r'u"\u0080"')
        from meld3 import _escape_cdata
        self.assertEqual('&#128;', _escape_cdata(a, 'ascii'))

    def test_escape_attrib(self):
        a = '< > &lt;&amp; &&apos; && &foo "" http://www.plope.com?foo=bar&bang=baz &#123;'
        from meld3 import _escape_attrib
        self.assertEqual('&lt; > &lt;&amp; &amp;&apos; &amp;&amp; &amp;foo &quot;&quot; http://www.plope.com?foo=bar&amp;bang=baz &#123;', _escape_attrib(a))

    def test_escape_attrib_unicodeerror(self):
        a = eval(r'u"\u0080"')
        from meld3 import _escape_attrib
        self.assertEqual('&#128;', _escape_attrib(a, 'ascii'))

def normalize_html(s):
    s = re.sub(r"[ \t]+", " ", s)
    s = re.sub(r"/>", ">", s)
    return s

def normalize_xml(s):
    s = re.sub(r"\s+", " ", s)
    s = re.sub(r"(?s)\s+<", "<", s)
    s = re.sub(r"(?s)>\s+", ">", s)
    return s

def test_suite():
    suite = unittest.TestSuite()
    suite.addTest( unittest.makeSuite( MeldAPITests ) )
    suite.addTest( unittest.makeSuite( MeldElementInterfaceTests ) )
    suite.addTest( unittest.makeSuite( ParserTests ) )
    suite.addTest( unittest.makeSuite( WriterTests) )
    suite.addTest( unittest.makeSuite( UtilTests) )
    return suite

def main():
    unittest.main(defaultTest='test_suite')

if __name__ == '__main__':
    main()

