001/**
002 *
003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved.
004 *
005 * This library is free software; you can redistribute it and/or
006 * modify it under the terms of the GNU Lesser General Public
007 * License as published by the Free Software Foundation; either 
008 * version 2.1 of the License, or (at your option) any later version.
009 * 
010 * This library is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013 * Lesser General Public License for more details.
014 * 
015 * You should have received a copy of the GNU Lesser General Public 
016 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
017 * 
018 **/
019package lucee.runtime.text.feed;
020
021import java.io.IOException;
022import java.io.InputStream;
023import java.util.HashMap;
024import java.util.Iterator;
025import java.util.Map;
026import java.util.Map.Entry;
027import java.util.Stack;
028
029import lucee.commons.io.IOUtil;
030import lucee.commons.io.SystemUtil;
031import lucee.commons.io.res.Resource;
032import lucee.commons.lang.StringUtil;
033import lucee.runtime.op.Caster;
034import lucee.runtime.text.xml.XMLUtil;
035import lucee.runtime.type.Array;
036import lucee.runtime.type.ArrayImpl;
037import lucee.runtime.type.CastableArray;
038import lucee.runtime.type.Collection;
039import lucee.runtime.type.Collection.Key;
040import lucee.runtime.type.KeyImpl;
041import lucee.runtime.type.Struct;
042import lucee.runtime.type.StructImpl;
043import lucee.runtime.type.util.KeyConstants;
044
045import org.xml.sax.Attributes;
046import org.xml.sax.InputSource;
047import org.xml.sax.Locator;
048import org.xml.sax.SAXException;
049import org.xml.sax.XMLReader;
050import org.xml.sax.ext.Locator2;
051import org.xml.sax.helpers.DefaultHandler;
052
053public final class FeedHandler extends DefaultHandler {
054
055        public final static String DEFAULT_SAX_PARSER="org.apache.xerces.parsers.SAXParser";
056
057        
058        private XMLReader xmlReader;
059
060        //private StringBuffer content=new StringBuffer();
061
062        private int deep=0;
063        private FeedStruct data;
064        private String path=""; 
065        private Collection.Key inside;
066        private Stack<FeedStruct> parents=new Stack<FeedStruct>();
067        private FeedDeclaration decl;
068        private Map<String,String> root=new HashMap<String,String>();
069        private boolean hasDC;
070        private boolean isAtom;
071
072        private boolean inAuthor;
073
074        private boolean inEntry;
075        
076        
077        /**
078         * Constructor of the class
079         * @param res
080         * @throws IOException
081         * @throws SAXException 
082         */
083        public FeedHandler(Resource res) throws IOException, SAXException {
084                InputStream is=null;
085                try {
086                        InputSource source=new InputSource(is=res.getInputStream());
087                        source.setSystemId(res.getPath());
088                        
089                        init(DEFAULT_SAX_PARSER,source);
090                } 
091                finally {
092                        IOUtil.closeEL(is);
093                }
094        }
095        public FeedHandler(InputSource is) throws IOException, SAXException {
096                init(DEFAULT_SAX_PARSER,is);
097                
098        }
099
100        /**
101         * Constructor of the class
102         * @param stream
103         * @throws IOException
104         * @throws SAXException 
105         */
106        public FeedHandler(InputStream stream) throws IOException, SAXException {
107                InputSource is=new InputSource(IOUtil.getReader(stream, SystemUtil.getCharset()));
108                init(DEFAULT_SAX_PARSER,is);
109        }
110        
111        private void init(String saxParser,InputSource is) throws SAXException, IOException     {
112                //print.out("is:"+is);
113                hasDC=false;
114                data=new FeedStruct();
115                xmlReader=XMLUtil.createXMLReader(saxParser);
116                xmlReader.setContentHandler(this);
117                xmlReader.setErrorHandler(this);
118                xmlReader.setDTDHandler(new DummyDTDHandler());
119                xmlReader.parse(is);
120    }
121        
122        /**
123         * @return the hasDC
124         */
125        public boolean hasDC() {
126                return hasDC;
127        }
128        public void setDocumentLocator(Locator locator) { 
129                if (locator instanceof Locator2) {
130                        Locator2 locator2 = (Locator2) locator;
131                        root.put("encoding", locator2.getEncoding());
132                } 
133        }
134
135        @Override
136        public void startElement(String uri, String name, String qName, Attributes atts) {
137                deep++;
138                
139                
140                if("entry".equals(name))inEntry=true;
141                else if("author".equals(name))inAuthor=true;
142                
143                if(qName.startsWith("dc:")){
144                        name="dc_"+name;
145                        hasDC=true;
146                }
147                //print.o("iniside("+deep+"):"+name+"->"+uri);
148                
149                
150                inside = KeyImpl.getInstance(name);
151                if(StringUtil.isEmpty(path))path=name;
152                else {
153                        path+="."+name;
154                }
155                if(decl==null){
156                        String decName=name;
157                        String version = atts.getValue("version");
158                        if("feed".equals(decName)) {
159                                if(!StringUtil.isEmpty(version))decName="atom_"+version;
160                                else decName="atom_1.0";
161                        }
162                        else {
163                                if(!StringUtil.isEmpty(version))decName+="_"+version;
164                        }
165                        decl=FeedDeclaration.getInstance(decName);
166                        root.put("version",decName);
167                        isAtom=decl.getType().equals("atom");
168                        
169                }
170                
171                
172                FeedStruct sct=new FeedStruct(path,inside,uri);
173                
174                // attributes
175                Map<String,String> attrs = getAttributes(atts, path);
176                if(attrs!=null){
177                        Entry<String, String>  entry;
178                        Iterator<Entry<String, String>> it = attrs.entrySet().iterator();
179                        sct.setHasAttribute(true);
180                        while(it.hasNext()){
181                                entry = it.next();
182                                sct.setEL(entry.getKey(), entry.getValue());
183                        }
184                }
185                
186                // assign
187                if(!isAtom || deep<4) {
188                        Object obj = data.get(inside, null);
189                        if(obj instanceof Array)        {
190                                ((Array)obj).appendEL(sct);
191                        }
192                        else if(obj instanceof FeedStruct){
193                                Array arr = new ArrayImpl();
194                                arr.appendEL(obj);
195                                arr.appendEL(sct);
196                                data.setEL(inside, arr);
197                        }
198                        else if(obj instanceof String){
199                                // wenn wert schon existiert wird castableArray in setContent erstellt
200                        }
201                        else {
202                                El el= decl.getDeclaration().get(path);
203                                if(el!=null && (el.getQuantity()==El.QUANTITY_0_N || el.getQuantity()==El.QUANTITY_1_N)){
204                                        Array arr = new ArrayImpl();
205                                        arr.appendEL(sct);
206                                        data.setEL(inside, arr);
207                                }
208                                else data.setEL(inside, sct);
209                                
210                        }
211                }
212                parents.add(data);
213                data=sct;
214                
215                //<enclosure url="http://www.scripting.com/mp3s/weatherReportDicksPicsVol7.mp3" length="6182912" type="audio/mpeg"/>
216        }
217        
218
219        public void endElement(String uri, String name, String qName) {
220                if("entry".equals(name))inEntry=false;
221                else if("author".equals(name))inAuthor=false;
222                deep--;
223                if(isAtom && deep>=(inEntry && inAuthor?4:3)) {
224                        String content = data.getString();
225                        Key[] keys = data.keys();
226                        StringBuilder sb=new StringBuilder();
227                        sb.append("<");
228                        sb.append(qName);
229                        
230                        // xmlns
231                        if(!parents.peek().getUri().equals(uri)) {
232                                sb.append(" xmlns=\"");
233                                sb.append(uri);
234                                sb.append("\"");
235                        }
236                        
237                        for(int i=0;i<keys.length;i++){
238                                sb.append(" ");
239                                sb.append(keys[i].getString());
240                                sb.append("=\"");
241                                sb.append(Caster.toString(data.get(keys[i],""),""));
242                                sb.append("\"");
243                                
244                        }
245                        
246                        
247                        if(!StringUtil.isEmpty(content)) {
248                                sb.append(">");
249                                sb.append(content);
250                                sb.append("</"+qName+">");
251                        }
252                        else sb.append("/>");
253                        
254                        
255                        
256                        
257                        
258                        
259                        data= parents.pop();
260                        data.append(sb.toString().trim());
261                        //setContent(sb.toString().trim());
262                        
263                        path=data.getPath();
264                        inside=data.getInside();
265                        return;
266                }
267                
268                
269                
270                //setContent(content.toString().trim());
271                setContent(data.getString().trim());
272                
273                
274                
275                data=parents.pop();
276                path=data.getPath();
277                inside=data.getInside();        
278        }
279        
280        public void characters (char ch[], int start, int length)       {
281                data.append(new String(ch,start,length));
282                //content.append(new String(ch,start,length));
283        }
284        
285        private void setContent(String value)   {
286                //print.out(path+":"+inside);
287                if(StringUtil.isEmpty(inside)) return;
288                        
289                        if(data.hasAttribute()) {
290                                if(!StringUtil.isEmpty(value))setEl(data,KeyConstants._value,value);
291                        }
292                        else {
293                                FeedStruct parent=parents.peek();
294                                setEl(parent,inside,value);
295                        }
296                                
297    }
298
299        private void setEl(Struct sct, Collection.Key key, String value) {
300                Object existing = sct.get(key,null);
301                
302                if(existing instanceof CastableArray){
303                        ((CastableArray)existing).appendEL(value);
304                }
305                else if(existing instanceof String){
306                        CastableArray ca=new CastableArray(existing);
307                        ca.appendEL(existing);
308                        ca.appendEL(value);
309                        sct.setEL(key,ca);
310                }
311                else 
312                        sct.setEL(key,Caster.toString(value));
313                
314                
315                /*if(existing instanceof Struct)sct.setEL(key,value);
316                else if(existing instanceof Array)((Array)existing).appendEL(value);
317                else if(existing!=null){
318                        CastableArray ca=new CastableArray(existing);
319                        ca.appendEL(existing);
320                        ca.appendEL(value);
321                        sct.setEL(key,ca);
322                }
323                else*/
324        }
325        
326        private Map<String,String> getAttributes(Attributes attrs, String path) {
327                El el =decl.getDeclaration().get(path);
328                
329                int len=attrs.getLength();
330                if((el==null || el.getAttrs()==null) && len==0) return null;
331                
332                Map<String,String> map=new HashMap<String,String>();
333                if(el!=null) {
334                        Attr[] defaults = el.getAttrs();
335                        if(defaults!=null) {
336                                for(int i=0;i<defaults.length;i++) {
337                                        if(defaults[i].hasDefaultValue())
338                                                map.put(defaults[i].getName(), defaults[i].getDefaultValue());
339                                }
340                        }
341                }
342                for(int i=0;i<len;i++) {
343                        map.put(attrs.getQName(i), attrs.getValue(i));
344                }
345                return map;
346        }
347        
348
349        /**
350         * @return the properties
351         */
352        public Struct getData() {
353                return data;
354        }
355        
356        
357        
358
359        @Override
360        public void endDocument() throws SAXException {
361                super.endDocument();
362                Struct def=new StructImpl();
363                Key[] entryLevel = decl.getEntryLevel();
364                for(int i=0;i<entryLevel.length;i++) {
365                        data=(FeedStruct) data.get(entryLevel[i],def);
366                }
367                data.putAll(root);
368        }
369        
370
371}