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;
023
024import lucee.commons.io.IOUtil;
025import lucee.commons.io.SystemUtil;
026import lucee.commons.io.res.Resource;
027import lucee.commons.lang.StringUtil;
028import lucee.runtime.exp.DatabaseException;
029import lucee.runtime.exp.PageException;
030import lucee.runtime.text.xml.XMLUtil;
031import lucee.runtime.type.Collection;
032import lucee.runtime.type.Collection.Key;
033import lucee.runtime.type.KeyImpl;
034import lucee.runtime.type.Query;
035import lucee.runtime.type.QueryImpl;
036import lucee.runtime.type.Struct;
037import lucee.runtime.type.StructImpl;
038
039import org.xml.sax.Attributes;
040import org.xml.sax.InputSource;
041import org.xml.sax.Locator;
042import org.xml.sax.SAXException;
043import org.xml.sax.XMLReader;
044import org.xml.sax.ext.Locator2;
045import org.xml.sax.helpers.DefaultHandler;
046
047public final class RSSHandler extends DefaultHandler {
048        
049        public final static String DEFAULT_SAX_PARSER="org.apache.xerces.parsers.SAXParser";
050
051        private static final Key RSSLINK = KeyImpl.intern("RSSLINK");
052        private static final Key CONTENT = KeyImpl.intern("CONTENT");
053
054        private static final Key LINK = KeyImpl.intern("LINK");
055        private static final Key DESCRIPTION = KeyImpl.intern("DESCRIPTION");
056        
057        private static Collection.Key[] COLUMNS=new Collection.Key[]{
058                KeyImpl.intern("AUTHOREMAIL"),
059                KeyImpl.intern("AUTHORNAME"),
060                KeyImpl.intern("AUTHORURI"),
061                KeyImpl.intern("CATEGORYLABEL"),
062                KeyImpl.intern("CATEGORYSCHEME"),
063                KeyImpl.intern("CATEGORYTERM"),
064                KeyImpl.intern("COMMENTS"),
065                CONTENT,
066                KeyImpl.intern("CONTENTMODE"),
067                KeyImpl.intern("CONTENTSRC"),
068                KeyImpl.intern("CONTENTTYPE"),
069                KeyImpl.intern("CONTRIBUTOREMAIL"),
070                KeyImpl.intern("CONTRIBUTORNAME"),
071                KeyImpl.intern("CONTRIBUTORURI"),
072                KeyImpl.intern("CREATEDDATE"),
073                KeyImpl.intern("EXPIRATIONDATE"),
074                KeyImpl.intern("ID"),
075                KeyImpl.intern("IDPERMALINK"),
076                KeyImpl.intern("LINKHREF"),
077                KeyImpl.intern("LINKHREFLANG"),
078                KeyImpl.intern("LINKLENGTH"),
079                KeyImpl.intern("LINKREL"),
080                KeyImpl.intern("LINKTITLE"),
081                KeyImpl.intern("LINKTYPE"),
082                KeyImpl.intern("PUBLISHEDDATE"),
083                KeyImpl.intern("RIGHTS"),
084                RSSLINK,
085                KeyImpl.intern("SOURCE"),
086                KeyImpl.intern("SOURCEURL"),
087                KeyImpl.intern("SUMMARY"),
088                KeyImpl.intern("SUMMARYMODE"),
089                KeyImpl.intern("SUMMARYSRC"),
090                KeyImpl.intern("SUMMARYTYPE"),
091                KeyImpl.intern("TITLE"),
092                KeyImpl.intern("TITLETYPE"),
093                KeyImpl.intern("UPDATEDDATE"),
094                KeyImpl.intern("URI"),
095                KeyImpl.intern("XMLBASE")
096        };
097        
098        
099        private XMLReader xmlReader;
100
101        private String lcInside;
102        private StringBuffer content=new StringBuffer();
103
104        private boolean insideImage;
105        private boolean insideItem;
106
107        private Struct image;
108        private Struct properties;
109        private Query items;
110
111        private Collection.Key inside;
112        
113        /**
114         * Constructor of the class
115         * @param res
116         * @throws IOException
117         * @throws SAXException 
118         * @throws DatabaseException 
119         */
120        public RSSHandler(Resource res) throws IOException, SAXException, DatabaseException {
121                InputStream is=null;
122                try {
123                        InputSource source=new InputSource(is=res.getInputStream());
124                        source.setSystemId(res.getPath());
125                        init(DEFAULT_SAX_PARSER,source);
126                } 
127                finally {
128                        IOUtil.closeEL(is);
129                }
130        }
131
132        /**
133         * Constructor of the class
134         * @param stream
135         * @throws IOException
136         * @throws SAXException 
137         * @throws DatabaseException 
138         */
139        public RSSHandler(InputStream stream) throws IOException, SAXException, DatabaseException {
140                InputSource is=new InputSource(IOUtil.getReader(stream, SystemUtil.getCharset()));
141                init(DEFAULT_SAX_PARSER,is);
142        }
143        
144        private void init(String saxParser,InputSource is) throws SAXException, IOException, DatabaseException  {
145                properties=new StructImpl();
146                items=new QueryImpl(COLUMNS,0,"query");
147                xmlReader=XMLUtil.createXMLReader(saxParser);
148                xmlReader.setContentHandler(this);
149                xmlReader.setErrorHandler(this);
150                
151                //xmlReader.setEntityResolver(new TagLibEntityResolver());
152                xmlReader.parse(is);
153                
154                //properties.setEL("encoding",is.getEncoding());
155                
156    }
157        
158        public void setDocumentLocator(Locator locator) { 
159                  if (locator instanceof Locator2) {
160                    Locator2 locator2 = (Locator2) locator;
161                    properties.setEL("encoding", locator2.getEncoding());
162                  } 
163                }
164
165        @Override
166        public void startElement(String uri, String name, String qName, Attributes atts) {
167                inside = KeyImpl.getInstance(qName);
168                lcInside=qName.toLowerCase();
169                if(lcInside.equals("image"))            insideImage=true;
170                else if(qName.equals("item"))   {
171                        items.addRow();
172                        insideItem=true;
173                }
174                else if(lcInside.equals("rss"))         {
175                        String version = atts.getValue("version");
176                        if(!StringUtil.isEmpty(version))
177                                properties.setEL("version", "rss_"+version);
178                }
179                
180                /* / cloud
181                else if(!insideItem && lcInside.equals("cloud"))        {
182                        
183                        
184                        
185                        String url = atts.getValue("url");
186                        if(!StringUtil.isEmpty(url))items.setAtEL("LINKHREF", items.getRowCount(), url);
187                        String length = atts.getValue("length");
188                        if(!StringUtil.isEmpty(length))items.setAtEL("LINKLENGTH", items.getRowCount(), length);
189                        String type = atts.getValue("type");
190                        if(!StringUtil.isEmpty(type))items.setAtEL("LINKTYPE", items.getRowCount(), type);
191                }*/
192                
193                
194                // enclosure
195                else if(insideItem && lcInside.equals("enclosure"))     {
196                        String url = atts.getValue("url");
197                        if(!StringUtil.isEmpty(url))items.setAtEL("LINKHREF", items.getRowCount(), url);
198                        String length = atts.getValue("length");
199                        if(!StringUtil.isEmpty(length))items.setAtEL("LINKLENGTH", items.getRowCount(), length);
200                        String type = atts.getValue("type");
201                        if(!StringUtil.isEmpty(type))items.setAtEL("LINKTYPE", items.getRowCount(), type);
202                }
203                
204                else if(atts.getLength()>0) {
205                        int len=atts.getLength();
206                        Struct sct=new StructImpl();
207                        for(int i=0;i<len;i++) {
208                                sct.setEL(atts.getQName(i), atts.getValue(i));
209                        }
210                        properties.setEL(inside, sct);
211                }
212                
213                //<enclosure url="http://www.scripting.com/mp3s/weatherReportDicksPicsVol7.mp3" length="6182912" type="audio/mpeg"/>
214        }
215    
216        @Override
217        public void endElement(String uri, String name, String qName) {
218                setContent(content.toString().trim());
219                content=new StringBuffer();
220                inside=null;
221                lcInside="";
222                
223                if(qName.equals("image")) insideImage=false;
224                if(qName.equals("item")) insideItem=false;
225        }
226        
227        
228    @Override
229        public void characters (char ch[], int start, int length)       {
230                content.append(new String(ch,start,length));
231        }
232        
233        private void setContent(String value)   {
234                if(StringUtil.isEmpty(lcInside)) return;
235                
236                if(insideImage) {
237                        if(image==null){
238                                image=new StructImpl();
239                                properties.setEL("image",image);
240                        }
241                        image.setEL(inside,value);
242                }
243                else if(insideItem)     {
244                        try {
245                                items.setAt(toItemColumn(inside), items.getRowCount(), value);
246                        } catch (PageException e) {
247                                //print.err(inside);
248                        }
249                        
250                }
251                else {
252                        if(!(StringUtil.isEmpty(value,true) && properties.containsKey(inside)))
253                                properties.setEL(inside,value);
254                }       
255    }
256
257        private Collection.Key toItemColumn(Collection.Key key) {
258                if(key.equalsIgnoreCase(LINK))                  return RSSLINK;
259                else if(key.equalsIgnoreCase(DESCRIPTION))return CONTENT;
260                return key;
261        }
262
263        /**
264         * @return the properties
265         */
266        public Struct getProperties() {
267                return properties;
268        }
269
270        /**
271         * @return the items
272         */
273        public Query getItems() {
274                return items;
275        }
276        
277        
278        /*public static void main(String[] args) throws IOException, SAXException {
279                ResourceProvider frp = ResourcesImpl.getFileResourceProvider();
280                Resource res = frp.getResource("/Users/mic/Projects/Lucee/webroot/jm/feed/092.xml");
281                RSSHandler rss=new RSSHandler(res);
282                print.out(rss.getProperties());
283                print.out(rss.getItems());
284                
285        }*/
286}