├── README.md ├── .gitignore ├── LICENSE ├── xoxolib.php ├── xoxo.py ├── xoxotest.php └── testxoxo.py /README.md: -------------------------------------------------------------------------------- 1 | # xoxo 2 | Code to transform back and forth between JSON and HTML outlines 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | *,cover 45 | 46 | # Translations 47 | *.mo 48 | *.pot 49 | 50 | # Django stuff: 51 | *.log 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # PyBuilder 57 | target/ 58 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | CC0 1.0 Universal 2 | 3 | Statement of Purpose 4 | 5 | The laws of most jurisdictions throughout the world automatically confer 6 | exclusive Copyright and Related Rights (defined below) upon the creator and 7 | subsequent owner(s) (each and all, an "owner") of an original work of 8 | authorship and/or a database (each, a "Work"). 9 | 10 | Certain owners wish to permanently relinquish those rights to a Work for the 11 | purpose of contributing to a commons of creative, cultural and scientific 12 | works ("Commons") that the public can reliably and without fear of later 13 | claims of infringement build upon, modify, incorporate in other works, reuse 14 | and redistribute as freely as possible in any form whatsoever and for any 15 | purposes, including without limitation commercial purposes. These owners may 16 | contribute to the Commons to promote the ideal of a free culture and the 17 | further production of creative, cultural and scientific works, or to gain 18 | reputation or greater distribution for their Work in part through the use and 19 | efforts of others. 20 | 21 | For these and/or other purposes and motivations, and without any expectation 22 | of additional consideration or compensation, the person associating CC0 with a 23 | Work (the "Affirmer"), to the extent that he or she is an owner of Copyright 24 | and Related Rights in the Work, voluntarily elects to apply CC0 to the Work 25 | and publicly distribute the Work under its terms, with knowledge of his or her 26 | Copyright and Related Rights in the Work and the meaning and intended legal 27 | effect of CC0 on those rights. 28 | 29 | 1. Copyright and Related Rights. A Work made available under CC0 may be 30 | protected by copyright and related or neighboring rights ("Copyright and 31 | Related Rights"). Copyright and Related Rights include, but are not limited 32 | to, the following: 33 | 34 | i. the right to reproduce, adapt, distribute, perform, display, communicate, 35 | and translate a Work; 36 | 37 | ii. moral rights retained by the original author(s) and/or performer(s); 38 | 39 | iii. publicity and privacy rights pertaining to a person's image or likeness 40 | depicted in a Work; 41 | 42 | iv. rights protecting against unfair competition in regards to a Work, 43 | subject to the limitations in paragraph 4(a), below; 44 | 45 | v. rights protecting the extraction, dissemination, use and reuse of data in 46 | a Work; 47 | 48 | vi. database rights (such as those arising under Directive 96/9/EC of the 49 | European Parliament and of the Council of 11 March 1996 on the legal 50 | protection of databases, and under any national implementation thereof, 51 | including any amended or successor version of such directive); and 52 | 53 | vii. other similar, equivalent or corresponding rights throughout the world 54 | based on applicable law or treaty, and any national implementations thereof. 55 | 56 | 2. Waiver. To the greatest extent permitted by, but not in contravention of, 57 | applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and 58 | unconditionally waives, abandons, and surrenders all of Affirmer's Copyright 59 | and Related Rights and associated claims and causes of action, whether now 60 | known or unknown (including existing as well as future claims and causes of 61 | action), in the Work (i) in all territories worldwide, (ii) for the maximum 62 | duration provided by applicable law or treaty (including future time 63 | extensions), (iii) in any current or future medium and for any number of 64 | copies, and (iv) for any purpose whatsoever, including without limitation 65 | commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes 66 | the Waiver for the benefit of each member of the public at large and to the 67 | detriment of Affirmer's heirs and successors, fully intending that such Waiver 68 | shall not be subject to revocation, rescission, cancellation, termination, or 69 | any other legal or equitable action to disrupt the quiet enjoyment of the Work 70 | by the public as contemplated by Affirmer's express Statement of Purpose. 71 | 72 | 3. Public License Fallback. Should any part of the Waiver for any reason be 73 | judged legally invalid or ineffective under applicable law, then the Waiver 74 | shall be preserved to the maximum extent permitted taking into account 75 | Affirmer's express Statement of Purpose. In addition, to the extent the Waiver 76 | is so judged Affirmer hereby grants to each affected person a royalty-free, 77 | non transferable, non sublicensable, non exclusive, irrevocable and 78 | unconditional license to exercise Affirmer's Copyright and Related Rights in 79 | the Work (i) in all territories worldwide, (ii) for the maximum duration 80 | provided by applicable law or treaty (including future time extensions), (iii) 81 | in any current or future medium and for any number of copies, and (iv) for any 82 | purpose whatsoever, including without limitation commercial, advertising or 83 | promotional purposes (the "License"). The License shall be deemed effective as 84 | of the date CC0 was applied by Affirmer to the Work. Should any part of the 85 | License for any reason be judged legally invalid or ineffective under 86 | applicable law, such partial invalidity or ineffectiveness shall not 87 | invalidate the remainder of the License, and in such case Affirmer hereby 88 | affirms that he or she will not (i) exercise any of his or her remaining 89 | Copyright and Related Rights in the Work or (ii) assert any associated claims 90 | and causes of action with respect to the Work, in either case contrary to 91 | Affirmer's express Statement of Purpose. 92 | 93 | 4. Limitations and Disclaimers. 94 | 95 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 96 | surrendered, licensed or otherwise affected by this document. 97 | 98 | b. Affirmer offers the Work as-is and makes no representations or warranties 99 | of any kind concerning the Work, express, implied, statutory or otherwise, 100 | including without limitation warranties of title, merchantability, fitness 101 | for a particular purpose, non infringement, or the absence of latent or 102 | other defects, accuracy, or the present or absence of errors, whether or not 103 | discoverable, all to the greatest extent permissible under applicable law. 104 | 105 | c. Affirmer disclaims responsibility for clearing rights of other persons 106 | that may apply to the Work or any use thereof, including without limitation 107 | any person's Copyright and Related Rights in the Work. Further, Affirmer 108 | disclaims responsibility for obtaining any necessary consents, permissions 109 | or other rights required for any use of the Work. 110 | 111 | d. Affirmer understands and acknowledges that Creative Commons is not a 112 | party to this document and has no duty or obligation with respect to this 113 | CC0 or use of the Work. 114 | 115 | For more information, please see 116 | 117 | 118 | -------------------------------------------------------------------------------- /xoxolib.php: -------------------------------------------------------------------------------- 1 | "; 23 | else 24 | $s .= "
    "; 25 | foreach ($struct as $key => $value) 26 | $s .= "
  1. " . makeXOXO($value) ."
  2. "; 27 | $s .="
"; 28 | } 29 | else if ($kind=='dictionary') 30 | { 31 | if (isset($struct['url'])) 32 | { 33 | $s .='"; 47 | unset($struct['url'],$struct['text']); 48 | } 49 | if (count($struct)) 50 | { 51 | $s .="
"; 52 | foreach ($struct as $key => $value) 53 | $s .= "
$key
". makeXOXO($value) . "
"; 54 | $s .= "
"; 55 | } 56 | } 57 | else 58 | $s .= "$struct"; 59 | #echo "returned $s\n"; 60 | return $s; 61 | } 62 | function toXOXO($struct,$addHTMLWrapper=FALSE,$cssUrl='') 63 | { 64 | if (getKind($struct) != 'list') 65 | $struct = array($struct); 66 | $xoxo = makeXOXO($struct,'xoxo'); 67 | if ($addHTMLWrapper) 68 | { 69 | $s= ''; 70 | if ($cssUrl) $s .=""; 71 | $s .="$xoxo"; 72 | return $s; 73 | } 74 | return $xoxo; 75 | } 76 | 77 | 78 | function pushStruct($struct,&$structstack,&$xostack,$structType) 79 | { 80 | if (is_array($struct) && $structType=='dict' && count($structstack) && is_array(end($structstack)) && isset($structstack[count($structstack)-1]['url']) && end($structstack) != end($xostack)) 81 | $xostack[] = &$structstack[count($structstack)-1]; # put back the
-made one for extra def's 82 | else 83 | { 84 | $structstack[]=$struct; 85 | $xostack[]=&$structstack[count($structstack)-1]; 86 | } 87 | } 88 | 89 | function fromXOXO($html) 90 | { 91 | $structs=array(); 92 | $xostack=array(); 93 | $textstack=array(''); 94 | $dumpStacks=0; 95 | $p = xml_parser_create(); 96 | xml_parse_into_struct($p, $html, $xoxoVals, $xoxoIndex); 97 | xml_parser_free($p); 98 | 99 | if($dumpStacks) 100 | { 101 | echo "
";
102 |         var_dump($xoxoVals);
103 |         var_dump($xoxoIndex);
104 |         echo "
"; 105 | } 106 | $howmany = sizeof($xoxoVals); 107 | 108 | #echo "
";
109 |     $x = $xoxoIndex['OL'];
110 |     for ($x=0;$x<$howmany;++$x)
111 |         {
112 |         if ($xoxoVals[$x]['tag'] == 'OL' || $xoxoVals[$x]['tag'] == 'DL'|| $xoxoVals[$x]['tag'] == 'UL')
113 |             {
114 |             if ($xoxoVals[$x]['tag'] == 'DL')
115 |                 $structType = 'dict';
116 |             else 
117 |                 $structType = 'list';
118 |             if ($xoxoVals[$x]['type'] == 'open')
119 |                 pushStruct(array(),$structs,$xostack,$structType);
120 |             if ($xoxoVals[$x]['type'] == 'close')
121 |                 array_pop($xostack);
122 |             if($dumpStacks)
123 |                 {
124 |                 echo $xoxoVals[$x]['type'] .' ' . $xoxoVals[$x]['tag'] .":\n";
125 |                 var_dump($structs);
126 |                 var_dump($xostack);
127 |                 }
128 |             }
129 |         if ($xoxoVals[$x]['tag'] == 'LI')
130 |             {
131 |             if ($xoxoVals[$x]['type'] == 'complete')
132 |                array_push($xostack[count($xostack)-1],$xoxoVals[$x]['value']);
133 |             if ($xoxoVals[$x]['type'] == 'close')
134 |                 {
135 |                 array_push($xostack[count($xostack)-1],array_pop($structs));
136 |                 }
137 |             if($dumpStacks)
138 |                 {
139 |                 echo $xoxoVals[$x]['type'] .' ' . $xoxoVals[$x]['tag'] .":\n";
140 |                 var_dump($structs);
141 |                 var_dump($xostack);
142 |                 }
143 |             }
144 | 
145 |         if ($xoxoVals[$x]['tag'] == 'DT')
146 |             {
147 |             if ($xoxoVals[$x]['type'] == 'complete')
148 |                 array_push($textstack,$xoxoVals[$x]['value']);
149 |             }
150 |         if ($xoxoVals[$x]['tag'] == 'DD')
151 |             {
152 |             if ($xoxoVals[$x]['type'] == 'complete')
153 |                 {
154 |                 $key = array_pop($textstack);
155 |                 $xostack[count($xostack)-1][$key] = $xoxoVals[$x]['value'];
156 |                 }
157 |             if ($xoxoVals[$x]['type'] == 'close')
158 |                 {
159 |                 $key = array_pop($textstack);
160 |                 $xostack[count($xostack)-1][$key] =array_pop($structs);
161 |                 }
162 |           if($dumpStacks)
163 |                 {
164 |                 echo $xoxoVals[$x]['type'] .' ' . $xoxoVals[$x]['tag'] .":\n";
165 |                 var_dump($structs);
166 |                 var_dump($xostack);
167 |                 }
168 |             }
169 |         if ($xoxoVals[$x]['tag'] == 'A')
170 |             {
171 |             if ($xoxoVals[$x]['type'] == 'complete')
172 |                 {
173 |                 $attrs = $xoxoVals[$x]['attributes'];
174 |                 $dict=array();
175 |                 foreach ($attrs as $key=> $value)
176 |                     {
177 |                     if ($key=='HREF')
178 |                         $dict['url'] = $value;
179 |                     else
180 |                         $dict[strtolower($key)] = $value;
181 |                     }
182 |                 $val = $xoxoVals[$x]['value'];
183 |                 if (isset($val) && ($val != $dict['title']) && ($val != $dict['url']))
184 |                     $dict['text'] = $val;
185 |                 pushStruct($dict,$structs,$xostack,'dict');
186 |                 array_pop($xostack);
187 |  
188 |                 if($dumpStacks)
189 |                     {
190 |                     echo $xoxoVals[$x]['type'] .' ' . $xoxoVals[$x]['tag'] .":\n";
191 |                     var_dump($structs);
192 |                     var_dump($xostack);
193 |                     }
194 |                }
195 |             }
196 |         }
197 |      #echo "
"; 198 | while (count($structs) == 1 && getKind($structs) == 'list') 199 | $structs = $structs[0]; 200 | return $structs; 201 | } 202 | ?> 203 | -------------------------------------------------------------------------------- /xoxo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | """xoxo.py - a utility module for transforming to and from the XHTMLOutlines format XOXO http://microformats.org/wiki/xoxo 4 | toXOXO takes a Python datastructure (tuples, lists or dictionaries, arbitrarily nested) and returns a XOXO representation of it. 5 | fromXOXO parses an XHTML file for a xoxo list and returns the structure 6 | """ 7 | __version__ = "0.9" 8 | __date__ = "2005-11-02" 9 | __author__ = "Kevin Marks " 10 | __copyright__ = "Copyright 2004-2006, Kevin Marks & Technorati" 11 | __license__ = "http://creativecommons.org/licenses/by/2.0/ CC-by-2.0], [http://www.apache.org/licenses/LICENSE-2.0 Apache 2.0" 12 | __credits__ = """Tantek Çelik and Mark Pilgrim for data structure""" 13 | __history__ = """ 14 | TODO: add tag 15 | TODO: add a proper profile link 16 | 0.9 smarter parsing for encoding and partial markup; fix dangling dictionary case 17 | 0.8 work in unicode then render to utf-8 18 | 0.7 initial encoding support - just utf-8 for now 19 | 0.6 support the special behaviour for url properties to/from <a> 20 | 0.5 fix some awkward side effects of whitespace and text outside our expected tags; simplify writing code 21 | 0.4 add correct XHTML headers so it validates 22 | 0.3 read/write version; fixed invalid nested list generation; 23 | 0.1 first write-only version 24 | """ 25 | 26 | try: 27 | True, False 28 | except NameError: 29 | True, False = not not 1, not 1 30 | containerTags={'ol':False,'ul':False,'dl':False} 31 | import sgmllib, urllib, urlparse, re,codecs 32 | 33 | def toUnicode(key): 34 | if type(key) == type(u'unicode'): 35 | uKey= key 36 | else: 37 | try: 38 | uKey=unicode(key,'utf-8') 39 | except: 40 | uKey=unicode(key,'windows_1252') 41 | return uKey 42 | 43 | def makeXOXO(struct,className=None): 44 | s=u'' 45 | if isinstance(struct,list) or isinstance(struct,tuple): 46 | if className: 47 | s += u'<ol class="%s">' % className 48 | else: 49 | s+= u"<ol>" 50 | for item in struct: 51 | s+=u"<li>" + makeXOXO(item,None)+"</li>" 52 | s +=u"</ol>" 53 | elif isinstance(struct,dict): 54 | d=struct.copy() 55 | if d.has_key('url') and not isinstance(d['url'],list): 56 | uURL=toUnicode(d['url']) 57 | s+=u'<a href="%s" ' % uURL 58 | text = d.get('text',d.get('title',uURL)) 59 | for attr in ('title','rel','type'): 60 | if d.has_key(attr): 61 | xVal = makeXOXO(d[attr],None) 62 | s +=u'%s="%s" ' % (attr,xVal) 63 | del d[attr] 64 | s +=u'>%s</a>' % makeXOXO(text,None) 65 | if d.has_key('text'): 66 | del d['text'] 67 | del d['url'] 68 | if len(d): 69 | s +=u"<dl>" 70 | for key,value in d.items(): 71 | xVal = makeXOXO(value,None) 72 | uKey=toUnicode(key) 73 | s+= u'<dt>%s</dt><dd>%s</dd>' % (uKey, xVal) 74 | s +=u"</dl>" 75 | elif type(struct) == type(u'unicode'): 76 | s+=struct 77 | else: 78 | if not type(struct)==type(' '): 79 | struct=str(struct) 80 | s += toUnicode(struct) 81 | return s 82 | class AttrParser(sgmllib.SGMLParser): 83 | def __init__(self): 84 | sgmllib.SGMLParser.__init__(self) 85 | self.text=[] 86 | self.encoding='utf-8' 87 | def cleanText(self,inText): 88 | if type(inText) == type(u'unicode'): 89 | inText = inText.encode(self.encoding,'replace') 90 | self.text=[] 91 | self.reset() 92 | self.feed(inText) 93 | return ''.join(self.text) 94 | def setEncoding(self,encoding): 95 | if 'ascii' in encoding: 96 | encoding='windows_1252' # so we don't throw an exception on high-bit set chars in there by mistake 97 | if encoding and not encoding =='text/html': 98 | try: 99 | canDecode = codecs.getdecoder(encoding) 100 | self.encoding = encoding 101 | except: 102 | try: 103 | encoding='japanese.' +encoding 104 | canDecode = codecs.getdecoder(encoding) 105 | self.encoding = encoding 106 | except: 107 | print "can't deal with encoding %s" % encoding 108 | 109 | def handle_entityref(self, ref): 110 | # called for each entity reference, e.g. for "©", ref will be "copy" 111 | # map through to unicode where we can 112 | try: 113 | entity =htmlentitydefs.name2codepoint[ref] 114 | self.handleUnicodeData(unichr(entity)) 115 | except: 116 | try: 117 | handle_charref(ref) # deal with char-ref's missing the '#' (see Akma) 118 | except: 119 | self.handle_data("&%s" % ref) 120 | 121 | def handle_charref(self, ref): 122 | # called for each character reference, e.g. for " ", ref will be "160" 123 | # Reconstruct the original character reference. 124 | try: 125 | if ref[0]=='x': 126 | self.handleUnicodeData(unichr(int(ref[1:],16))) 127 | else: 128 | self.handleUnicodeData(unichr(int(ref))) 129 | except: 130 | self.handle_data("&#%s" % ref) 131 | 132 | # called for each block of plain text, i.e. outside of any tag and 133 | # not containing any character or entity references 134 | def handle_data(self, text): 135 | if type(text)==type(u' '): 136 | self.handleUnicodeData(text) 137 | if self.encoding== 'utf-8': 138 | try: 139 | uText = unicode(text,self.encoding) #utf-8 is pretty clear when it is wrong 140 | except: 141 | uText = unicode(text,'windows_1252','ignore') # and this is the likely wrongness 142 | else: 143 | uText = unicode(text,self.encoding,'replace') # if they have really broken encoding, (eg lots of shift-JIS blogs) 144 | self.handleUnicodeData(uText) 145 | def handleUnicodeData(self, uText): 146 | self.text.append(uText) 147 | 148 | class xoxoParser(AttrParser): 149 | def __init__(self): 150 | AttrParser.__init__(self) 151 | self.structs=[] 152 | self.xostack=[] 153 | self.textstack=[''] 154 | self.attrparse = AttrParser() 155 | def normalize_attrs(self, attrs): 156 | attrs = [(k.lower(), self.attrparse.cleanText(v)) for k, v in attrs] 157 | attrs = [(k, k in ('rel','type') and v.lower() or v) for k, v in attrs] 158 | return attrs 159 | def setEncoding(self,encoding): 160 | AttrParser.setEncoding(self,encoding) 161 | self.attrparse.setEncoding(encoding) 162 | def pushStruct(self,struct): 163 | if type(struct) == type({}) and len(struct)==0 and len(self.structs) and type(self.structs[-1]) == type({}) and self.structs[-1].has_key('url') and self.structs[-1] != self.xostack[-1]: 164 | self.xostack.append(self.structs[-1]) # put back the <a>-made one for extra def's 165 | else: 166 | self.structs.append(struct) 167 | self.xostack.append(self.structs[-1]) 168 | def do_meta(self, attributes): 169 | atts = dict(self.normalize_attrs(attributes)) 170 | #print atts.encode('utf-8') 171 | if atts.has_key('http-equiv'): 172 | if atts['http-equiv'].lower() == "content-type": 173 | if atts.has_key('content'): 174 | encoding = atts['content'].split('charset=')[-1] 175 | self.setEncoding(encoding) 176 | def start_a(self,attrs): 177 | attrsD = dict(self.normalize_attrs(attrs)) 178 | attrsD['url']= attrsD.get('href','') 179 | if attrsD.has_key('href'): 180 | del attrsD['href'] 181 | self.pushStruct(attrsD) 182 | self.textstack.append('') 183 | def end_a(self): 184 | val = self.textstack.pop() 185 | if val: 186 | if self.xostack[-1].get('title','') == val: 187 | val='' 188 | if self.xostack[-1]['url'] == val: 189 | val='' 190 | if val: 191 | self.xostack[-1]['text']=val 192 | self.xostack.pop() 193 | def start_dl(self,attrs): 194 | self.pushStruct({}) 195 | def end_dl(self): 196 | self.xostack.pop() 197 | def start_ol(self,attrs): 198 | self.pushStruct([]) 199 | def end_ol(self): 200 | self.xostack.pop() 201 | def start_ul(self,attrs): 202 | self.pushStruct([]) 203 | def end_ul(self): 204 | self.xostack.pop() 205 | def start_li(self,attrs): 206 | self.textstack.append('') 207 | def end_li(self): 208 | val = self.textstack.pop() 209 | while ( self.structs[-1] != self.xostack[-1]): 210 | val = self.structs.pop() 211 | self.xostack[-1].append(val) 212 | if type(val) == type(' ') or type(val) == type(u' '): 213 | self.xostack[-1].append(val) 214 | def start_dt(self,attrs): 215 | self.textstack.append('') 216 | def end_dt(self): 217 | pass 218 | def start_dd(self,attrs): 219 | self.textstack.append('') 220 | def end_dd(self): 221 | val = self.textstack.pop() 222 | key = self.textstack.pop() 223 | if self.structs[-1] != self.xostack[-1]: 224 | val = self.structs.pop() 225 | self.xostack[-1][key]=val 226 | def handleUnicodeData(self, text): 227 | if len(self.stack) and containerTags.get(self.stack[-1],True): #skip text not within an element 228 | self.textstack[-1] += text 229 | def toXOXO(struct,addHTMLWrapper=False,cssUrl=''): 230 | if type(struct) ==type((1,))or type(struct) ==type([1,]): 231 | inStruct = struct 232 | else: 233 | inStruct = [struct] 234 | if addHTMLWrapper: 235 | s= u'''<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN 236 | http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 237 | <html xmlns="http://www.w3.org/1999/xhtml"><head profile=""><meta http-equiv="Content-Type" content="text/html; charset=utf-8" />''' 238 | if cssUrl: 239 | s+=u'<style type="text/css" >@import "%s";</style>' % cssUrl 240 | s+=u"</head><body>%s</body></html>" % makeXOXO(inStruct,'xoxo') 241 | return s.encode('utf-8') 242 | else: 243 | return makeXOXO(inStruct,'xoxo').encode('utf-8') 244 | 245 | def fromXOXO(html): 246 | parser = xoxoParser() 247 | #parser.feed(unicode(html,'utf-8')) 248 | parser.feed(html) 249 | #print parser.structs 250 | structs=[struct for struct in parser.structs if struct] 251 | #print structs 252 | while (len(structs) ==1 and type(structs)==type([1,])): 253 | structs=structs[0] 254 | return structs 255 | -------------------------------------------------------------------------------- /xoxotest.php: -------------------------------------------------------------------------------- 1 | <? 2 | include("xoxolib.php"); 3 | function assertEqual($testname,$str1,$str2) 4 | { 5 | if ($str1 == $str2) 6 | echo "<h3>√ $testname </h3>"; 7 | else 8 | { 9 | echo "<h3><big>☹</big> $testname failed</h3>"; 10 | echo "<dl><dt>expected</dt>\n<dd>$str1</dd>\n<dt>returned</dt>\n<dd>$str2</dd>\n<dl>"; 11 | 12 | } 13 | } 14 | 15 | function assertArrayEqual($testname,$expected,$returned) 16 | { 17 | if ($expected == $returned) 18 | echo "<h3>√ $testname </h3>"; 19 | else 20 | { 21 | echo "<h3><big>☹</big> $testname failed</h3>"; 22 | echo "<dl><dt>expected</dt>\n<dd><pre>"; 23 | var_dump($expected); 24 | echo "</pre></dd>\n<dt>returned</dt>\n<dd><pre>"; 25 | var_dump($returned); 26 | echo "</pre></dd>\n<dl>"; 27 | 28 | } 29 | } 30 | 31 | function failIfEqual($testname,$str1,$str2) 32 | { 33 | if ($str1 != $str2) 34 | echo "<h3>√ $testname</h3>"; 35 | else 36 | { 37 | echo "<h3><big>☹</big> $testname failed</h3>"; 38 | echo "<dl><dt>both were</dt><dd>$str1</dd><dl>"; 39 | 40 | } 41 | } 42 | 43 | 44 | $l = array('1','2','3'); 45 | $html = toXOXO($l); 46 | assertEqual('make xoxo from list','<ol class="xoxo"><li>1</li><li>2</li><li>3</li></ol>',$html); 47 | 48 | $s = 'test'; 49 | $html = toXOXO($s); 50 | assertEqual("make xoxo from a string",'<ol class="xoxo"><li>test</li></ol>',$html); 51 | $htmlwrap = toXOXO($s,TRUE); 52 | failIfEqual("make sure wrapped and unwrapped differ",html,htmlwrap); 53 | assertEqual("make wrapped xoxo from a string",'<!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"><head profile=""><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /></head><body><ol class="xoxo"><li>test</li></ol></body></html>',$htmlwrap); 54 | $csswrap = toXOXO($s,TRUE,"reaptest.css"); 55 | assertEqual("make wrapped xoxo with css link from a string",'<!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"><head profile=""><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><style type="text/css" >@import "reaptest.css";</style></head><body><ol class="xoxo"><li>test</li></ol></body></html>',$csswrap); 56 | $l2 = array('1',array('2','3')); 57 | $html = toXOXO($l2); 58 | assertEqual('make xoxo from nested list','<ol class="xoxo"><li>1</li><li><ol><li>2</li><li>3</li></ol></li></ol>',$html); 59 | $d = array(test=>'1'); 60 | $html = toXOXO($d); 61 | assertEqual('make xoxo from 1-element dictionary','<ol class="xoxo"><li><dl><dt>test</dt><dd>1</dd></dl></li></ol>',$html); 62 | $d = array(test=>'1',name=>Kevin); 63 | $html = toXOXO($d); 64 | assertEqual('make xoxo from dictionary','<ol class="xoxo"><li><dl><dt>test</dt><dd>1</dd><dt>name</dt><dd>Kevin</dd></dl></li></ol>',$html); 65 | $d = array('url'=>'http://example.com/more.xoxo','title'=>'sample url','type'=>"text/xml",'rel'=>'help','text'=>'an example'); 66 | $html = toXOXO($d); 67 | assertEqual('make xoxo from dictionary with url','<ol class="xoxo"><li><a href="http://example.com/more.xoxo" title="sample url" rel="help" type="text/xml" >an example</a></li></ol>',$html); 68 | $d = array('url'=>'http://example.com/more.xoxo','title'=>'sample url','type'=>"text/xml",'rel'=>'help','text'=>'an example','thing'=>'and another thing...'); 69 | $html = toXOXO($d); 70 | assertEqual('make xoxo from dictionary with url and thing','<ol class="xoxo"><li><a href="http://example.com/more.xoxo" title="sample url" rel="help" type="text/xml" >an example</a><dl><dt>thing</dt><dd>and another thing...</dd></dl></li></ol>',$html); 71 | $d = array('url'=>'http://example.com/more.xoxo','title'=>'sample url','type'=>"text/xml",'rel'=>'help','text'=>'an example','list'=>array('and', 'another','thing...')); 72 | $html = toXOXO($d); 73 | assertEqual('make xoxo from dictionary with url and list','<ol class="xoxo"><li><a href="http://example.com/more.xoxo" title="sample url" rel="help" type="text/xml" >an example</a><dl><dt>list</dt><dd><ol><li>and</li><li>another</li><li>thing...</li></ol></dd></dl></li></ol>',$html); 74 | $l = array('3',array('a'=>'2')); 75 | $html = toXOXO($l); 76 | assertEqual('make xoxo from dict in list','<ol class="xoxo"><li>3</li><li><dl><dt>a</dt><dd>2</dd></dl></li></ol>',$html); 77 | 78 | $l = array('3','2','1'); 79 | $html = toXOXO($l); 80 | $newdl= fromXOXO($html); 81 | assertArrayEqual('list to xoxo and back',$l,$newdl); 82 | $l = array('1',array('a','b')); 83 | $html = toXOXO($l); 84 | $newdl= fromXOXO($html); 85 | assertArrayEqual('list of lists to xoxo and back',$l,$newdl); 86 | 87 | $l= array('3',array('a','2'),array('b',array('1',array('c','4')))); 88 | $html = toXOXO($l); 89 | $newdl= fromXOXO($html); 90 | assertArrayEqual('list of list of lists to xoxo and back',$l,$newdl); 91 | $d = array(test=>'1',name=>Kevin); 92 | $html = toXOXO($d); 93 | $newd= fromXOXO($html); 94 | assertArrayEqual('dictionary to xoxo and back',$d,$newd); 95 | 96 | $l = array('3',array('a'=>'2'),array('b'=>'1','c'=>'4')); 97 | $html = toXOXO($l); 98 | $newdl= fromXOXO($html); 99 | assertArrayEqual('list of dicts to xoxo and back',$l,$newdl); 100 | assertEqual('list of dicts to xoxo and back',$html,toXOXO($newdl)); 101 | $l = array('one'=>array('a'=>'2','b'=>'3'),'two'=>array('c'=>'4')); 102 | $html = toXOXO($l); 103 | $newdl= fromXOXO($html); 104 | assertArrayEqual('dict of dicts to xoxo and back',$l,$newdl); 105 | assertEqual('dict of dicts to xoxo and back',$html,toXOXO($newdl)); 106 | $l = array('one'=>array('a'=>'2','b'=>'3'),'url'=>'http://example.com'); 107 | $html = toXOXO($l); 108 | $newdl= fromXOXO($html); 109 | assertArrayEqual('dict of dicts with url to xoxo and back',$l,$newdl); 110 | assertEqual('dict of dicts with url to xoxo and back',$html,toXOXO($newdl)); 111 | $d = array('test'=> array('1','2'), 112 | 'name'=> 'Kevin','nestlist'=> array('a',array('b','c')), 113 | 'nestdict'=>array('e'=>'6','f'=>'7')); 114 | $html = toXOXO($d); 115 | $newd= fromXOXO($html); 116 | assertArrayEqual('dictionary of lists to xoxo and back',$d,$newd); 117 | 118 | $d=fromXOXO('<ol>bad<li><dl>worse<dt>good</dt><dd>buy</dd> now</dl></li></ol>'); 119 | assertArrayEqual('make sure text outside <li> etc is ignored',array(good=>buy),$d); 120 | 121 | $l=fromXOXO('<ol><li>bad<dl><dt>good</dt><dd>buy</dd></dl>worse</li><li>bag<ol><li>OK</li></ol>fish</li></ol>'); 122 | assertArrayEqual('make sure text within <li> but outside a subcontainer is ignored',array(array(good=>buy),array('OK')),$l); 123 | 124 | $xoxoSample= "<ol class='xoxo'> 125 | <li> 126 | <dl> 127 | <dt>text</dt> 128 | <dd>item 1</dd> 129 | <dt>description</dt> 130 | <dd> This item represents the main point we're trying to make.</dd> 131 | <dt>url</dt> 132 | <dd>http://example.com/more.xoxo</dd> 133 | <dt>title</dt> 134 | <dd>title of item 1</dd> 135 | <dt>type</dt> 136 | <dd>text/xml</dd> 137 | <dt>rel</dt> 138 | <dd>help</dd> 139 | </dl> 140 | </li> 141 | </ol>"; 142 | $d = fromXOXO($xoxoSample); 143 | $d2=array('text'=>'item 1', 144 | 'description'=>" This item represents the main point we're trying to make.", 145 | 'url'=>'http://example.com/more.xoxo', 146 | 'title'=>'title of item 1', 147 | 'type'=>'text/xml', 148 | 'rel'=>'help'); 149 | assertArrayEqual('unmung some xoxo with spaces in and check result is right',$d2,$d); 150 | 151 | $xoxoSample= "<ol class='xoxo'> 152 | <li> 153 | <dl> 154 | <dt>text</dt> 155 | <dd>item 1</dd> 156 | <dt>url</dt> 157 | <dd>http://example.com/more.xoxo</dd> 158 | <dt>title</dt> 159 | <dd>title of item 1</dd> 160 | <dt>type</dt> 161 | <dd>text/xml</dd> 162 | <dt>rel</dt> 163 | <dd>help</dd> 164 | </dl> 165 | </li> 166 | </ol>"; 167 | $d = fromXOXO($xoxoSample); 168 | $smartxoxoSample= "<ol class=\"xoxo\"> 169 | <li><a href=\"http://example.com/more.xoxo\" 170 | title=\"title of item 1\" 171 | type=\"text/xml\" 172 | rel=\"help\">item 1</a> 173 | <!-- note how the \"text\" property is simply the contents of the <a> element --> 174 | </li> 175 | </ol>"; 176 | $d2 = fromXOXO($smartxoxoSample); 177 | assertArrayEqual('unmung some xoxo with <a href= rel= etc in and check result is right',$d,$d2); 178 | $xoxoSample= "<ol class='xoxo'> 179 | <li> 180 | <dl> 181 | <dt>text</dt> 182 | <dd>item 1</dd> 183 | <dt>description</dt> 184 | <dd> This item represents the main point we're trying to make.</dd> 185 | <dt>url</dt> 186 | <dd>http://example.com/more.xoxo</dd> 187 | <dt>title</dt> 188 | <dd>title of item 1</dd> 189 | <dt>type</dt> 190 | <dd>text/xml</dd> 191 | <dt>rel</dt> 192 | <dd>help</dd> 193 | </dl> 194 | </li> 195 | </ol>"; 196 | $d = fromXOXO($xoxoSample); 197 | $smartxoxoSample= "<ol class=\"xoxo\"> 198 | <li><a href=\"http://example.com/more.xoxo\" 199 | title=\"title of item 1\" 200 | type=\"text/xml\" 201 | rel=\"help\">item 1</a> 202 | <!-- note how the \"text\" property is simply the contents of the <a> element --> 203 | <dl> 204 | <dt>description</dt> 205 | <dd> This item represents the main point we're trying to make.</dd> 206 | </dl> 207 | </li> 208 | </ol>"; 209 | $d2 = fromXOXO($smartxoxoSample); 210 | assertArrayEqual('unmung some xoxo with <a href= rel= etc in and check result is right',$d,$d2); 211 | 212 | $d=array('url'=>'http://example.com/more.xoxo','title'=>'sample url','type'=>"text/xml",'rel'=>'help','text'=>'an example'); 213 | $html=toXOXO($d); 214 | assertArrayEqual('round trip url to href to url',$d,fromXOXO($html)); 215 | 216 | $d=array('url'=>'http://example.com/more.xoxo','title'=>'sample url','type'=>"text/xml",'rel'=>'help'); 217 | $html=toXOXO($d); 218 | assertArrayEqual('round trip url to href to url (no text)',$d,fromXOXO($html)); 219 | 220 | $d=array('url'=>'http://example.com/more.xoxo'); 221 | $html=toXOXO($d); 222 | assertArrayEqual('round trip url to href to url (just url)',$d,fromXOXO($html)); 223 | $kmattn=<<<ENDATTN 224 | <ol class="xoxo"><li><a href="http://www.boingboing.net/" title="Boing Boing Blog" >Boing Boing Blog</a><dl><dt>alturls</dt><dd><ol><li><a href="http://boingboing.net/rss.xml" >xmlurl</a></li></ol></dd><dt>description</dt><dd>Boing Boing Blog</dd></dl></li><li><a href="http://www.financialcryptography.com/" title="Financial Cryptography" >Financial Cryptography</a><dl><dt>alturls</dt><dd><ol><li><a href="http://www.financialcryptography.com/mt/index.rdf" >xmlurl</a></li></ol></dd><dt>description</dt><dd>Financial Cryptography</dd></dl></li><li><a href="http://hublog.hubmed.org/" title="HubLog" >HubLog</a><dl><dt>alturls</dt><dd><ol><li><a href="http://hublog.hubmed.org/index.xml" >xmlurl</a></li><li><a href="http://hublog.hubmed.org/foaf.rdf" >foafurl</a></li></ol></dd><dt>description</dt><dd>HubLog</dd></dl></li></ol> 225 | ENDATTN; 226 | $d=fromXOXO($kmattn); 227 | $newattn = toXOXO($d); 228 | $d2=fromXOXO($newattn); 229 | assertArrayEqual('attention double round-trip',$d,$d2); 230 | assertEqual('attention triple round-trip',$newattn,toXOXO($d2)); 231 | assertEqual('attention one round-trip',$kmattn,$newattn); 232 | $d=array(array(url=>"http://www.boingboing.net/",title=>"Boing Boing Blog","alturls"=>array(array("url"=>"http://boingboing.net/rss.xml","text"=> 233 | "xmlurl")),"description"=>"Boing Boing Blog"),array(url=>"http://www.financialcryptography.com/",title=>"Financial Cryptography","alturls"=>array(array("url"=>"http://www.financialcryptography.com/mt/index.rdf","text"=> 234 | "xmlurl")),"description"=>"Financial Cryptography")); 235 | $attn=<<<ENDATTN 236 | <ol class="xoxo"><li><a href="http://www.boingboing.net/" title="Boing Boing Blog" >Boing Boing Blog</a><dl><dt>alturls</dt><dd><ol><li><a href="http://boingboing.net/rss.xml" >xmlurl</a></li></ol></dd><dt>description</dt><dd>Boing Boing Blog</dd></dl></li><li><a href="http://www.financialcryptography.com/" title="Financial Cryptography" >Financial Cryptography</a><dl><dt>alturls</dt><dd><ol><li><a href="http://www.financialcryptography.com/mt/index.rdf" >xmlurl</a></li></ol></dd><dt>description</dt><dd>Financial Cryptography</dd></dl></li></ol> 237 | ENDATTN; 238 | assertEqual('attention encode',$attn,toXOXO($d)); 239 | 240 | 241 | ?> 242 | -------------------------------------------------------------------------------- /testxoxo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | """testxoxo.py 4 | Unit tests for xoxo.py 5 | This file tests the functions in xoxo.py 6 | The underlying model here is http://diveintopython.org/unit_testing/index.html 7 | 8 | run from command line with 9 | python testxoxo.py -v 10 | """ 11 | import xoxo 12 | reload(xoxo) 13 | import unittest 14 | 15 | class xoxoTestCases(unittest.TestCase): 16 | 17 | def testSimpleList(self): 18 | '''make a xoxo file from a list''' 19 | l = ['1','2','3'] 20 | html = xoxo.toXOXO(l) 21 | self.assertEqual(html,'<ol class="xoxo"><li>1</li><li>2</li><li>3</li></ol>') 22 | def testNestedList(self): 23 | '''make a xoxo file from a list with a list in''' 24 | l = ['1',['2','3']] 25 | html = xoxo.toXOXO(l) 26 | self.assertEqual(html,'<ol class="xoxo"><li>1</li><li><ol><li>2</li><li>3</li></ol></li></ol>') 27 | 28 | def testDictionary(self): 29 | '''make a xoxo file from a dictionary''' 30 | d = {'test':'1','name':'Kevin'} 31 | html = xoxo.toXOXO(d) 32 | self.assertEqual(html,'<ol class="xoxo"><li><dl><dt>test</dt><dd>1</dd><dt>name</dt><dd>Kevin</dd></dl></li></ol>') 33 | 34 | def testSingleItem(self): 35 | '''make a xoxo file from a string''' 36 | l = "test" 37 | html = xoxo.toXOXO(l) 38 | self.assertEqual(html,'<ol class="xoxo"><li>test</li></ol>') 39 | 40 | def testWrapDiffers(self): 41 | '''make a xoxo file from a string with and without html wrapper and check they are different''' 42 | l = "test" 43 | html = xoxo.toXOXO(l) 44 | htmlwrap = xoxo.toXOXO(l,addHTMLWrapper=True) 45 | self.failIfEqual(html,htmlwrap) 46 | 47 | def testWrapSingleItem(self): 48 | '''make a wrapped xoxo file from a string''' 49 | l = "test" 50 | html = xoxo.toXOXO(l,addHTMLWrapper=True) 51 | self.assertEqual(html,'''<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN 52 | http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 53 | <html xmlns="http://www.w3.org/1999/xhtml"><head profile=""><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /></head><body><ol class="xoxo"><li>test</li></ol></body></html>''') 54 | 55 | def testWrapItemWithCSS(self): 56 | '''make a wrapped xoxo file from a string''' 57 | l = "test" 58 | html = xoxo.toXOXO(l,addHTMLWrapper=True,cssUrl='reaptest.css') 59 | self.assertEqual(html,'''<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN 60 | http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 61 | <html xmlns="http://www.w3.org/1999/xhtml"><head profile=""><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><style type="text/css" >@import "reaptest.css";</style></head><body><ol class="xoxo"><li>test</li></ol></body></html>''') 62 | 63 | def testDictionaryRoundTrip(self): 64 | ''' make a dictionary into a xoxo file and back again; check it is the same''' 65 | d = {'test':'1','name':'Kevin'} 66 | html = xoxo.toXOXO(d) 67 | newd = xoxo.fromXOXO(html) 68 | self.assertEqual(d,newd) 69 | 70 | def testDictionaryWithURLRoundTrip(self): 71 | ''' make a dictionary wiht an url in into a xoxo file and back again; check it is the same''' 72 | d = {'url':'http://example.com','name':'Kevin'} 73 | html = xoxo.toXOXO(d) 74 | newd = xoxo.fromXOXO(html) 75 | self.assertEqual(d,newd) 76 | def testNestedDictionaryRoundTrip(self): 77 | ''' make a dictionary with a dict in into a xoxo file and back again; check it is the same''' 78 | d = {'test':'1','inner':{'name':'Kevin'}} 79 | html = xoxo.toXOXO(d) 80 | newd = xoxo.fromXOXO(html) 81 | self.assertEqual(d,newd) 82 | def testNestedDictionaryWithURLRoundTrip(self): 83 | ''' make a dictionary with an url and a dict into a xoxo file and back again; check it is the same''' 84 | d = {'url':'http://example.com','inner':{'name':'Kevin'}} 85 | html = xoxo.toXOXO(d) 86 | newd = xoxo.fromXOXO(html) 87 | self.assertEqual(d,newd) 88 | def testNestedDictionariesWithURLsRoundTrip(self): 89 | ''' make a dictionary with an url and a dict with an url into a xoxo file and back again; check it is the same''' 90 | d = {'url':'http://example.com','inner':{'name':'Kevin','url':'http://slashdot.org'}} 91 | html = xoxo.toXOXO(d) 92 | newd = xoxo.fromXOXO(html) 93 | self.assertEqual(d,newd) 94 | def testListRoundTrip(self): 95 | ''' make a list into a xoxo file and back again; check it is the same''' 96 | l = ['3','2','1'] 97 | html = xoxo.toXOXO(l) 98 | newdl= xoxo.fromXOXO(html) 99 | self.assertEqual(l,newdl) 100 | def testListofDictsRoundTrip(self): 101 | ''' make a list of Dicts into a xoxo file and back again; check it is the same''' 102 | l = ['3',{'a':'2'},{'b':'1','c':'4'}] 103 | html = xoxo.toXOXO(l) 104 | newdl= xoxo.fromXOXO(html) 105 | self.assertEqual(l,newdl) 106 | def testListofListsRoundTrip(self): 107 | ''' make a list of Lists into a xoxo file and back again; check it is the same''' 108 | l = ['3',['a','2'],['b',['1',['c','4']]]] 109 | html = xoxo.toXOXO(l) 110 | newdl= xoxo.fromXOXO(html) 111 | self.assertEqual(l,newdl) 112 | def testDictofListsRoundTrip(self): 113 | ''' make a dict with lists in into a xoxo file and back again; check it is the same''' 114 | d = {'test':['1','2'], 115 | 'name':'Kevin', 116 | 'nestlist':['a',['b','c']], 117 | 'nestdict':{'e':'6','f':'7'}} 118 | html = xoxo.toXOXO(d) 119 | newd = xoxo.fromXOXO(html) 120 | self.assertEqual(d,newd) 121 | 122 | def testXOXOjunkInContainers(self): 123 | '''make sure text outside <li> etc is ignored''' 124 | d=xoxo.fromXOXO('<ol>bad<li><dl>worse<dt>good</dt><dd>buy</dd> now</dl></li></ol>') 125 | self.assertEqual(d,{'good': 'buy'}) 126 | 127 | def testXOXOjunkInElements(self): 128 | '''make sure text within <li> but outside a subcontainer is ignored''' 129 | l=xoxo.fromXOXO('<ol><li>bad<dl><dt>good</dt><dd>buy</dd></dl>worse</li><li>bag<ol><li>OK</li></ol>fish</li></ol>') 130 | self.assertEqual(l,[{'good': 'buy'},['OK']]) 131 | 132 | def testXOXOWithSpacesAndNewlines(self): 133 | '''unmung some xoxo with spaces in and check result is right''' 134 | xoxoSample= '''<ol class='xoxo'> 135 | <li> 136 | <dl> 137 | <dt>text</dt> 138 | <dd>item 1</dd> 139 | <dt>description</dt> 140 | <dd> This item represents the main point we're trying to make.</dd> 141 | <dt>url</dt> 142 | <dd>http://example.com/more.xoxo</dd> 143 | <dt>title</dt> 144 | <dd>title of item 1</dd> 145 | <dt>type</dt> 146 | <dd>text/xml</dd> 147 | <dt>rel</dt> 148 | <dd>help</dd> 149 | </dl> 150 | </li> 151 | </ol>''' 152 | d = xoxo.fromXOXO(xoxoSample) 153 | d2={'text':'item 1', 154 | 'description':" This item represents the main point we're trying to make.", 155 | 'url':'http://example.com/more.xoxo', 156 | 'title':'title of item 1', 157 | 'type':'text/xml', 158 | 'rel':'help' 159 | } 160 | xoxoAgain = xoxo.toXOXO(d) 161 | self.assertEqual(d,d2) 162 | #this needs a smarter whitespace-sensitive comparison 163 | #self.assertEqual(xoxoSample,xoxoAgain) 164 | 165 | def testSpecialAttributeDecoding(self): 166 | '''unmung some xoxo with <a href=' rel= etc in and check result is right''' 167 | xoxoSample= '''<ol class='xoxo'> 168 | <li> 169 | <dl> 170 | <dt>text</dt> 171 | <dd>item 1</dd> 172 | <dt>url</dt> 173 | <dd>http://example.com/more.xoxo</dd> 174 | <dt>title</dt> 175 | <dd>title of item 1</dd> 176 | <dt>type</dt> 177 | <dd>text/xml</dd> 178 | <dt>rel</dt> 179 | <dd>help</dd> 180 | </dl> 181 | </li> 182 | </ol>''' 183 | d = xoxo.fromXOXO(xoxoSample) 184 | smartxoxoSample= '''<ol class='xoxo'> 185 | <li><a href="http://example.com/more.xoxo" 186 | title="title of item 1" 187 | type="text/xml" 188 | rel="help">item 1</a> 189 | <!-- note how the "text" property is simply the contents of the <a> element --> 190 | </li> 191 | </ol>''' 192 | d2 = xoxo.fromXOXO(smartxoxoSample) 193 | self.assertEqual(d,d2) 194 | def testSpecialAttributeAndDLDecoding(self): 195 | '''unmung some xoxo with <a href=' rel= etc in plus a <dl> in the same item and check result is right''' 196 | xoxoSample= '''<ol class="xoxo"> 197 | <li> 198 | <dl> 199 | <dt>text</dt> 200 | <dd>item 1</dd> 201 | <dt>description</dt> 202 | <dd> This item represents the main point we're trying to make.</dd> 203 | <dt>url</dt> 204 | <dd>http://example.com/more.xoxo</dd> 205 | <dt>title</dt> 206 | <dd>title of item 1</dd> 207 | <dt>type</dt> 208 | <dd>text/xml</dd> 209 | <dt>rel</dt> 210 | <dd>help</dd> 211 | </dl> 212 | </li> 213 | </ol>''' 214 | d = xoxo.fromXOXO(xoxoSample) 215 | smartxoxoSample= '''<ol class="xoxo"> 216 | <li><a href="http://example.com/more.xoxo" 217 | title="title of item 1" 218 | type="text/xml" 219 | rel="help">item 1</a> 220 | <!-- note how the "text" property is simply the contents of the <a> element --> 221 | <dl> 222 | <dt>description</dt> 223 | <dd> This item represents the main point we're trying to make.</dd> 224 | </dl> 225 | </li> 226 | </ol>''' 227 | d2 = xoxo.fromXOXO(smartxoxoSample) 228 | self.assertEqual(d,d2) 229 | def testSpecialAttributeEncode(self): 230 | '''check it makes an <a href with a url parameter''' 231 | d={'url':'http://example.com/more.xoxo','title':'sample url','type':"text/xml",'rel':'help','text':'an example'} 232 | html=xoxo.toXOXO(d) 233 | expectedHTML= '<ol class="xoxo"><li><a href="http://example.com/more.xoxo" title="sample url" rel="help" type="text/xml" >an example</a></li></ol>' 234 | self.assertEqual(html,expectedHTML) 235 | 236 | def testSpecialAttributeRoundTripFull(self): 237 | '''check it makes an <a href with a url parameter''' 238 | d={'url':'http://example.com/more.xoxo','title':'sample url','type':"text/xml",'rel':'help','text':'an example'} 239 | html=xoxo.toXOXO(d) 240 | self.assertEqual(d,xoxo.fromXOXO(html)) 241 | def testSpecialAttributeRoundTripNoText(self): 242 | '''check it makes an <a href with a url parameter and no text attribute''' 243 | d={'url':'http://example.com/more.xoxo','title':'sample url','type':"text/xml",'rel':'help'} 244 | html=xoxo.toXOXO(d) 245 | self.assertEqual(d,xoxo.fromXOXO(html)) 246 | def testSpecialAttributeRoundTripNoTextOrTitle(self): 247 | '''check it makes an <a href with a url parameter and no text or title attribute''' 248 | d={'url':'http://example.com/more.xoxo'} 249 | html=xoxo.toXOXO(d) 250 | self.assertEqual(d,xoxo.fromXOXO(html)) 251 | def testAttentionRoundTrip(self): 252 | '''check nested <a> and <dl> and <a> are preserved''' 253 | kmattn='''<ol class="xoxo"><li><a href="http://www.boingboing.net/" title="Boing Boing Blog" >Boing Boing Blog</a><dl><dt>alturls</dt><dd><ol><li><a href="http://boingboing.net/rss.xml" >xmlurl</a></li></ol></dd><dt>description</dt><dd>Boing Boing Blog</dd></dl></li><li><a href="http://www.financialcryptography.com/" title="Financial Cryptography" >Financial Cryptography</a><dl><dt>alturls</dt><dd><ol><li><a href="http://www.financialcryptography.com/mt/index.rdf" >xmlurl</a></li></ol></dd><dt>description</dt><dd>Financial Cryptography</dd></dl></li><li><a href="http://hublog.hubmed.org/" title="HubLog" >HubLog</a><dl><dt>alturls</dt><dd><ol><li><a href="http://hublog.hubmed.org/index.xml" >xmlurl</a></li><li><a href="http://hublog.hubmed.org/foaf.rdf" >foafurl</a></li></ol></dd><dt>description</dt><dd>HubLog</dd></dl></li></ol>'''; 254 | d = xoxo.fromXOXO(kmattn) 255 | newattn = xoxo.toXOXO(d) 256 | d2 = xoxo.fromXOXO(newattn) 257 | self.assertEqual(newattn,xoxo.toXOXO(d2)) 258 | self.assertEqual(d,d2) 259 | self.assertEqual(kmattn,newattn) 260 | 261 | def testUnicodeRoundtrip(self): 262 | '''check unicode characters can go to xoxo and back''' 263 | src=unicode('Tantek \xc3\x87elik and a snowman \xe2\x98\x83','utf-8') 264 | html = xoxo.toXOXO(src) 265 | self.assertEqual(src,xoxo.fromXOXO(html)) 266 | def testUtf8Roundtrip(self): 267 | '''check utf8 characters can go to xoxo and back''' 268 | src='Tantek \xc3\x87elik and a snowman \xe2\x98\x83' 269 | html = xoxo.toXOXO(src) 270 | self.assertEqual(src,xoxo.fromXOXO(html).encode('utf-8')) 271 | def testWindows1252Roundtrip(self): 272 | '''check 1252 characters can go to xoxo and back''' 273 | src='This is an evil\xa0space' 274 | html = xoxo.toXOXO(src) 275 | self.assertEqual(src,xoxo.fromXOXO(html).encode('windows-1252')) 276 | def testUrlList(self): 277 | '''handle the mf2 case where url is a list''' 278 | d={'url':['http://example.com/']} 279 | html = xoxo.toXOXO(d) 280 | self.assertEqual(html,'<ol class="xoxo"><li><dl><dt>url</dt><dd><ol><li>http://example.com/</li></ol></dd></dl></li></ol>') 281 | 282 | 283 | if __name__ == "__main__": 284 | unittest.main() 285 | else: 286 | runner = unittest.TextTestRunner() 287 | suite = unittest.makeSuite(xoxoTestCases,'test') 288 | runner.run(suite) 289 | 290 | --------------------------------------------------------------------------------