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

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

Starting to put stubB handling into ows/browse.
Starting to add trackback support.

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                self.defns=self.realns[n[1][1:-1]]
70            elif n[0][0:6]=='xmlns:':
71                self.realns[n[1][1:-1]]=n[0][6:]
72           
73    def tohtml(self):
74        '''Lightweight HTML pretty printing of elementTree elements
75           and formatted using a css something like this:
76            ===
77            DIV.xmlElem {PADDING-LEFT: 20px;}
78            .xmlAttrVal {COLOR:Red; }
79            .xmlAttrTyp {COLOR:Green; }
80            .xmlElemTag {COLOR:Blue; }
81        .   highlight {BACKGROUND-COLOR:Yellow; }
82            ===
83            Line number is not yet implemented.
84            '''
85        lt,gt='<b>&lt;</b>','<b>&gt;</b>'
86        def span(x,c): return '<span class="%s">%s</span>'%(c,x)
87        def div(x,c): return '<div class="%s">%s</div>'%(c,x)
88        def fix(x): 
89            if x is None: return ''
90            return x
91        def et2html(elem):   
92            strAttrib=''
93            for att in elem.attrib:
94                strAttrib+=' %s="%s"'%(span(att,'xmlAttrTyp'),span(elem.attrib[att],'xmlAttrVal'))
95            result='%s%s%s%s%s'%(lt,span(elem.tag,"xmlElemTag"),strAttrib,gt,fix(elem.text))
96            children=len(elem)
97            if children:
98                for item in elem:
99                    result+=et2html(item)
100                result+='%s%s/%s%s'%(fix(elem.tail),lt,span(elem.tag,'xmlElemTag'),gt)
101            else:
102                result+='%s/%s%s'%(lt,span(elem.tag,'xmlElemTag'),gt)
103            return div(result,'xmlElem')
104           
105        ss=et2html(self.tree)
106        h=''
107        if self.root:h='%s%s %s="%s" %s="%s"%s'%(
108            lt,'?xml',span('version','xmlAttrTyp'),'1.0',span('encoding','xmlAttrTyp'),'utf-8',gt)
109        ss=self.__fixXML(ss)
110        if self.realns=={}: return h+ss
111        return h+self.__nsfixpretty(ss,span)
112         
113    def __nsfixpretty(self,s,span):
114        ''' Yet another careful fix '''
115        for ns in self.realns:
116            r='{%s}'%ns
117            if self.realns[ns]=='default':
118                s=s.replace(r,'')
119            else:
120                s=s.replace(r,'%s:'%self.realns[ns])
121        if self.realns=={}: return s
122        # at this point we have no namespace list at the top
123        rightArrow=s.find('</span>') # this is just after the tag, where we do want the namespace list
124        nslist={} 
125        for ns in self.realns: nslist[self.realns[ns]]=ns
126        r=' %s="%s"'%(span('xmlns','xmlAttrTyp'),span(nslist['default'],'xmlAttrVal'))
127        for ns in nslist:
128            if ns<>'default': r+=' %s="%s"'%(span('xmlns:%s'%ns,'xmlAttrTyp'),span(nslist[ns],'xmlAttrVal'))
129        return s[:rightArrow]+r+s[rightArrow:]
130       
131    def __updatens(self):
132        ''' Update the element tree namespace map with our own map '''
133        # *c*ElementTree doesn't have this update method (or at
134        # least I can't find it), so you have to import ElementTree and call it on
135        # that, then it all mysteriously works in cElementTree...
136
137        pET._namespace_map.update(self.realns)
138
139         
140    def __str__(self):
141        ### actually we should consider whether this was in the input or not
142        h=''
143        if self.root:h='<?xml version="1.0" encoding="utf-8">'
144        ss=ET.tostring(self.tree)
145        ### ugly as sin, what happens if default: is in the text? We really ought to do this
146        #properly in iterparse on loading the thing ...
147        if self.realns=={}: return h+ss
148        return self.__fixns(h,ss)
149       
150    def __fixns(self,h,ss):
151        ''' Fix the namespaces after ET has produced a string '''
152        ss=ss.replace('default:','')
153        for ns in self.realns:
154            r='xmlns:%s="%s"'%(self.realns[ns],ns)
155            ss=ss.replace(r,'')
156        #now fix the namespaces back in the first element
157        rightArrow=ss.find('>')
158        #reorder dictionary (I know I didn't need to do it but
159        #code readability is worth a millisecond or two.
160        nslist={}
161        for ns in self.realns: nslist[self.realns[ns]]=ns
162        r='xmlns="%s"'%nslist['default']
163        for ns in nslist:
164            if ns<>'default': r+=' xmlns:%s="%s"'%(ns,nslist[ns])
165        h+=ss[:rightArrow]+r+ss[rightArrow:]
166        return h
167     
168    def __fixXML(self,s):
169        #first those nasty ampersands
170        s=re.sub(r'&(?!\w+;)', '&amp;', s)
171        #and now orphan > < signs
172        if self.r1 is None:
173            self.r1=re.compile('<([^>]*(<|$))')
174            self.r2=re.compile('((^|>)[^<]*)>')
175        old=''
176        while s != old:
177            old=s
178            s=self.r1.sub(r'&lt;\1',s)
179            s=self.r2.sub(r'\1&gt;',s)
180        return s
181       
182    def __distributens(self,xpathExpression):
183        ''' Actually we only support tag finding in this '''
184        tags=xpathExpression.split('/')
185        new=''
186        for t in tags: 
187            if t[1]<>'{': 
188                new+=self.defns+t+'/'
189            else:
190                new+=t+'/'
191        new=new[0:-1]
192        return new
193       
194    def getText(self,elem,xpathExpression,multiple=0):
195        ''' Get a text object sensibly, given ET API for xml doesn't handle
196        namespaces gracefully '''
197        if elem is None: 
198            if multiple:
199                return ['',]
200            else: return '' 
201        if multiple:
202                r=elem.findall(self.__distributens(xpathExpression))
203        else:
204                r=[elem.find(self.__distributens(xpathExpression)),]
205        try:  # if element is None, this should fail ...
206                rr=[]
207                for i in r:
208                    t=i.text
209                    if t is not None: 
210                        rr.append(t)
211                    else: rr.append('')
212        except:
213                rr=['',]
214        if multiple: 
215                return rr
216        else: return rr[0]
217         
218         
219if __name__=="__main__":
220   
221    import unittest
222   
223    class TestCase(unittest.TestCase): 
224       
225        def setup(self):
226            self.ss='''<?xml version="1.0" encoding="UTF-8"?>
227                <Dataset xmlns:swe="http://www.opengis.net/swe" xmlns:gml="http://www.opengis.net/gml"
228                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:moles="http://ndg.nerc.ac.uk/moles"
229                 xmlns:om="http://www.opengis.net/om" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://ndg.nerc.ac.uk/csml"
230                 id="FGPfF9i0"><CSMLFeatureCollection gml:id="AfEj15o6"/><om:blah>blahvalue</om:blah><foo>foovalue</foo></Dataset>'''
231                 
232        def testns(self):
233            ''' Make sure we extract the namespaces correctly '''
234            self.setup()
235            x=xmlHandler(self.ss,string=1)
236            self.assertEqual(x.realns,{'http://www.opengis.net/om':'om', 'http://www.opengis.net/gml':'gml', 
237            'http://ndg.nerc.ac.uk/csml':'default', 'http://www.opengis.net/swe':'swe', 
238            'http://www.w3.org/1999/xlink':'xlink', 
239            'http://www.w3.org/2001/XMLSchema-instance':'xsi', 'http://ndg.nerc.ac.uk/moles':'moles'})
240           
241        def teststr(self):
242            ''' Make sure we can get a string version after loading '''
243            self.setup()
244            x=xmlHandler(self.ss,string=1)
245            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))
246           
247        def testorphans(self):
248            ''' Make sure we can handle orphan characters properly '''
249            s='<data> 1<2</data>'
250            x=xmlHandler(s,string=1)
251            self.assertEqual('<data> 1&lt;2</data>',str(x))
252
253        def testAmpsersand1(self):
254            ''' Can we load unescaped ampersands?'''
255            s='<data> a & b </data>'
256            x=xmlHandler(s,string=1)
257            self.assertEqual('<data> a &amp; b </data>',str(x))
258           
259        def testAmpersand2(self):
260            ''' Do we output proper things? '''
261            s='<data> 2 &amp; 3 &lt; 8 </data>'
262            x=xmlHandler(s,string=1)
263            self.assertEqual('<data> 2 &amp; 3 &lt; 8 </data>',str(x))
264           
265        def testPrettyPrint(self):
266            ''' Test a simple pretty print '''
267            s='<?xml version="1.0" encoding="utf-8"?><data><element>stuff</element></data>'
268            x=xmlHandler(s,string=1)
269            h=x.tohtml() # only testing the mechanics, not the result
270           
271           
272        #turn off the test
273        def AtestRealDIF(self):
274            ''' Test a real DIF from the ndgRetrieve stable '''
275            f='ndgRetrieve.badc.nerc.ac.uk__DIF__dataent_11738019833217179.debug.xml'
276            x=xmlHandler(f)
277            y=str(x)  # only testing the mechanics, not the result
278           
279           
280        def testDIF(self):
281            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>'''
282            x=xmlHandler(s,string=1)
283            print x.realns
284            print str(x)
285            h=x.tohtml()
286            print h
287           
288    unittest.main()
289           
290           
Note: See TracBrowser for help on using the repository browser.