source: TI05-delivery/ows_framework/trunk/ows_server/ows_server/models/xmlHandler2.py @ 2641

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI05-delivery/ows_framework/trunk/ows_server/ows_server/models/xmlHandler2.py@2641
Revision 2641, 10.1 KB checked in by lawrence, 12 years ago (diff)

Fixed major bug in xmlHandler2, and retrieve now
respects CSML security. CSML documents are large
so need to put caching in place.

Line 
1try:
2    from xml.etree import cElementTree as ET
3    from xml.etree import ElementTree as pET
4except ImportError:
5    try:
6        import cElementTree as ET
7        import ElementTree as pET
8    except ImportError:
9        # For some reason when I install ElementTree with easyinstall it
10        # is called "elementree".
11        import elementtree.ElementTree as ET
12        pET=ET
13       
14from xml.parsers.expat import ExpatError
15import StringIO, re
16
17class xmlHandler:
18
19    def __init__(self,xml,string=0):
20        ''' Open an xml file (or string) and
21           - if necessary correct nasty characters and/or orphans before passing to ET
22           - load up an element-tree
23           - collect a namespace map '''
24       
25        self.r1=None   # we only use the regex if we need them
26       
27        if string:
28            self.xmls=xml
29            xmlf=None
30        else:
31            xmlf=xml
32            self.xmls=file(xmlf,'r').read()
33       
34        self.realns={}           
35        self.__getns()
36       
37        if xmlf is None: 
38            xmlf=StringIO.StringIO(self.xmls.encode('utf-8'))
39       
40        try:
41            self.tree=ET.parse(xmlf).getroot()
42        except SyntaxError:
43            self.xmls=self.__fixXML(self.xmls)
44            xmlf=StringIO.StringIO(self.xmls.encode('utf-8'))
45            self.tree=ET.parse(xmlf).getroot()
46           
47        self.__updatens()
48   
49    def __getns(self):
50       
51        ''' Get what the user intended out of elementtree namespaces '''
52        #ought to do this with a regular expression, but needs must
53        # or bettter yet, use iterparse in the first place, but that seemed slow.
54       
55
56        if self.xmls[0:19]=='<?xml version="1.0"':
57            self.root=1
58            hb1=self.xmls.find('>')+1
59        else:
60            hb1=0
61            self.root=0
62       
63        hb=self.xmls[hb1:].find('<')+1,self.xmls[hb1:].find('>')
64        s=self.xmls[hb1+hb[0]:hb1+hb[1]]
65        for w in s.split():
66            n=w.split('=')
67            if n[0]=='xmlns':
68                self.realns[n[1][1:-1]]='default'
69            elif n[0][0:6]=='xmlns:':
70                self.realns[n[1][1:-1]]=n[0][6:]
71           
72    def tohtml(self):
73        '''Lightweight HTML pretty printing of elementTree elements
74           and formatted using a css something like this:
75            ===
76            DIV.xmlElem {PADDING-LEFT: 20px;}
77            .xmlAttrVal {COLOR:Red; }
78            .xmlAttrTyp {COLOR:Green; }
79            .xmlElemTag {COLOR:Blue; }
80        .   highlight {BACKGROUND-COLOR:Yellow; }
81            ===
82            Line number is not yet implemented.
83            '''
84        lt,gt='<b>&lt;</b>','<b>&gt;</b>'
85        def span(x,c): return '<span class="%s">%s</span>'%(c,x)
86        def div(x,c): return '<div class="%s">%s</div>'%(c,x)
87        def fix(x): 
88            if x is None: return ''
89            return x
90        def et2html(elem):   
91            strAttrib=''
92            for att in elem.attrib:
93                strAttrib+=' %s="%s"'%(span(att,'xmlAttrTyp'),span(elem.attrib[att],'xmlAttrVal'))
94            result='%s%s%s%s%s'%(lt,span(elem.tag,"xmlElemTag"),strAttrib,gt,fix(elem.text))
95            children=len(elem)
96            if children:
97                for item in elem:
98                    result+=et2html(item)
99                result+='%s%s/%s%s'%(fix(elem.tail),lt,span(elem.tag,'xmlElemTag'),gt)
100            else:
101                result+='%s/%s%s'%(lt,span(elem.tag,'xmlElemTag'),gt)
102            return div(result,'xmlElem')
103           
104        ss=et2html(self.tree)
105        h=''
106        if self.root:h='%s%s %s="%s" %s="%s"%s'%(
107            lt,'?xml',span('version','xmlAttrTyp'),'1.0',span('encoding','xmlAttrTyp'),'utf-8',gt)
108        ss=self.__fixXML(ss)
109        if self.realns=={}: return h+ss
110        return h+self.__nsfixpretty(ss,span)
111         
112    def __nsfixpretty(self,s,span):
113        ''' Yet another careful fix '''
114        for ns in self.realns:
115            r='{%s}'%ns
116            if self.realns[ns]=='default':
117                s=s.replace(r,'')
118            else:
119                s=s.replace(r,'%s:'%self.realns[ns])
120        if self.realns=={}: return s
121        # at this point we have no namespace list at the top
122        rightArrow=s.find('</span>') # this is just after the tag, where we do want the namespace list
123        nslist={} 
124        for ns in self.realns: nslist[self.realns[ns]]=ns
125        r=' %s="%s"'%(span('xmlns','xmlAttrTyp'),span(nslist['default'],'xmlAttrVal'))
126        for ns in nslist:
127            if ns<>'default': r+=' %s="%s"'%(span('xmlns:%s'%ns,'xmlAttrTyp'),span(nslist[ns],'xmlAttrVal'))
128        return s[:rightArrow]+r+s[rightArrow:]
129       
130    def __updatens(self):
131        ''' Update the element tree namespace map with our own map '''
132        # *c*ElementTree doesn't have this update method (or at
133        # least I can't find it), so you have to import ElementTree and call it on
134        # that, then it all mysteriously works in cElementTree...
135
136        pET._namespace_map.update(self.realns)
137
138         
139    def __str__(self):
140        ### actually we should consider whether this was in the input or not
141        h=''
142        if self.root:h='<?xml version="1.0" encoding="utf-8">'
143        ss=ET.tostring(self.tree)
144        ### ugly as sin, what happens if default: is in the text? We really ought to do this
145        #properly in iterparse on loading the thing ...
146        if self.realns=={}: return h+ss
147        return self.__fixns(h,ss)
148       
149    def __fixns(self,h,ss):
150        ''' Fix the namespaces after ET has produced a string '''
151        ss=ss.replace('default:','')
152        for ns in self.realns:
153            r='xmlns:%s="%s"'%(self.realns[ns],ns)
154            ss=ss.replace(r,'')
155        #now fix the namespaces back in the first element
156        rightArrow=ss.find('>')
157        #reorder dictionary (I know I didn't need to do it but
158        #code readability is worth a millisecond or two.
159        nslist={}
160        for ns in self.realns: nslist[self.realns[ns]]=ns
161        r='xmlns="%s"'%nslist['default']
162        for ns in nslist:
163            if ns<>'default': r+=' xmlns:%s="%s"'%(ns,nslist[ns])
164        h+=ss[:rightArrow]+r+ss[rightArrow:]
165        return h
166     
167    def __fixXML(self,s):
168        #first those nasty ampersands
169        s=re.sub(r'&(?!\w+;)', '&amp;', s)
170        #and now orphan > < signs
171        if self.r1 is None:
172            self.r1=re.compile('<([^>]*(<|$))')
173            self.r2=re.compile('((^|>)[^<]*)>')
174        old=''
175        while s != old:
176            old=s
177            s=self.r1.sub(r'&lt;\1',s)
178            s=self.r2.sub(r'\1&gt;',s)
179        return s
180     
181         
182         
183if __name__=="__main__":
184   
185    import unittest
186   
187    class TestCase(unittest.TestCase): 
188       
189        def setup(self):
190            self.ss='''<?xml version="1.0" encoding="UTF-8"?>
191                <Dataset xmlns:swe="http://www.opengis.net/swe" xmlns:gml="http://www.opengis.net/gml"
192                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:moles="http://ndg.nerc.ac.uk/moles"
193                 xmlns:om="http://www.opengis.net/om" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://ndg.nerc.ac.uk/csml"
194                 id="FGPfF9i0"><CSMLFeatureCollection gml:id="AfEj15o6"/><om:blah>blahvalue</om:blah><foo>foovalue</foo></Dataset>'''
195                 
196        def testns(self):
197            ''' Make sure we extract the namespaces correctly '''
198            self.setup()
199            x=xmlHandler(self.ss,string=1)
200            self.assertEqual(x.realns,{'http://www.opengis.net/om':'om', 'http://www.opengis.net/gml':'gml', 
201            'http://ndg.nerc.ac.uk/csml':'default', 'http://www.opengis.net/swe':'swe', 
202            'http://www.w3.org/1999/xlink':'xlink', 
203            'http://www.w3.org/2001/XMLSchema-instance':'xsi', 'http://ndg.nerc.ac.uk/moles':'moles'})
204           
205        def teststr(self):
206            ''' Make sure we can get a string version after loading '''
207            self.setup()
208            x=xmlHandler(self.ss,string=1)
209            self.assertEqual('<?xml version="1.0" encoding="utf-8"><Dataset id="FGPfF9i0" xmlns="http://ndg.nerc.ac.uk/csml" xmlns:om="http://www.opengis.net/om" xmlns:gml="http://www.opengis.net/gml" xmlns:swe="http://www.opengis.net/swe" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:moles="http://ndg.nerc.ac.uk/moles"><CSMLFeatureCollection gml:id="AfEj15o6"  /><om:blah >blahvalue</om:blah><foo>foovalue</foo></Dataset>',str(x))
210           
211        def testorphans(self):
212            ''' Make sure we can handle orphan characters properly '''
213            s='<data> 1<2</data>'
214            x=xmlHandler(s,string=1)
215            self.assertEqual('<data> 1&lt;2</data>',str(x))
216
217        def testAmpsersand1(self):
218            ''' Can we load unescaped ampersands?'''
219            s='<data> a & b </data>'
220            x=xmlHandler(s,string=1)
221            self.assertEqual('<data> a &amp; b </data>',str(x))
222           
223        def testAmpersand2(self):
224            ''' Do we output proper things? '''
225            s='<data> 2 &amp; 3 &lt; 8 </data>'
226            x=xmlHandler(s,string=1)
227            self.assertEqual('<data> 2 &amp; 3 &lt; 8 </data>',str(x))
228           
229        def testPrettyPrint(self):
230            ''' Test a simple pretty print '''
231            s='<?xml version="1.0" encoding="utf-8"?><data><element>stuff</element></data>'
232            x=xmlHandler(s,string=1)
233            h=x.tohtml() # only testing the mechanics, not the result
234           
235           
236        #turn off the test
237        def AtestRealDIF(self):
238            ''' Test a real DIF from the ndgRetrieve stable '''
239            f='ndgRetrieve.badc.nerc.ac.uk__DIF__dataent_11738019833217179.debug.xml'
240            x=xmlHandler(f)
241            y=str(x)  # only testing the mechanics, not the result
242           
243           
244        def testDIF(self):
245            s='''<DIF xmlns="http://gcmd.gsfc.nasa.gov/Aboutus/xml/dif/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><Entry_ID>badc.nerc.ac.uk__DIF__dataent_11738019833217179</Entry_ID></DIF>'''
246            x=xmlHandler(s,string=1)
247            print x.realns
248            print str(x)
249            h=x.tohtml()
250            print h
251           
252    unittest.main()
253           
254           
Note: See TracBrowser for help on using the repository browser.