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.tag;
020
021import java.io.IOException;
022import java.io.Reader;
023import java.io.StringReader;
024import java.util.ArrayList;
025
026import lucee.commons.io.IOUtil;
027import lucee.commons.io.res.Resource;
028import lucee.commons.io.res.type.http.HTTPResource;
029import lucee.commons.io.res.util.ResourceUtil;
030import lucee.commons.lang.Pair;
031import lucee.commons.lang.StringUtil;
032import lucee.runtime.PageContextImpl;
033import lucee.runtime.exp.ApplicationException;
034import lucee.runtime.exp.ExpressionException;
035import lucee.runtime.exp.PageException;
036import lucee.runtime.ext.tag.TagImpl;
037import lucee.runtime.functions.dateTime.GetHttpTimeString;
038import lucee.runtime.net.proxy.ProxyData;
039import lucee.runtime.net.proxy.ProxyDataImpl;
040import lucee.runtime.op.Caster;
041import lucee.runtime.op.Decision;
042import lucee.runtime.text.feed.FeedHandler;
043import lucee.runtime.text.feed.FeedQuery;
044import lucee.runtime.text.xml.XMLUtil;
045import lucee.runtime.type.Array;
046import lucee.runtime.type.Query;
047import lucee.runtime.type.Struct;
048import lucee.runtime.type.util.ListUtil;
049
050import org.xml.sax.InputSource;
051import org.xml.sax.SAXException;
052
053public final class Feed extends TagImpl {
054
055        private static final int ACTION_READ = 0;
056        private static final int ACTION_CREATE = 1;
057        
058        private static final int TYPE_AUTO = 0;
059        private static final int TYPE_RSS = 1;
060        private static final int TYPE_ATOM = 2;
061        
062        
063        
064        private int action=ACTION_READ;
065        private Struct columnMap=null;
066        private Resource enclosureDir=null;
067        private boolean ignoreEnclosureError=false;
068        private Object name=null;
069        private Resource outputFile=null;
070        private boolean overwrite=false;
071        private boolean overwriteEnclosure=false;
072        private Object properties=null;
073        private Object query=null;
074        private Resource source=null;
075        private int timeout=-1;
076        private int type=TYPE_AUTO;
077        private String userAgent=null;
078        private String xmlVar=null;
079
080        private String proxyPassword=null;
081        private int proxyPort=80;
082        private String proxyServer=null;
083        private String proxyUser=null;
084        private String charset=null;
085        
086        @Override
087        public void release() {
088                charset=null;
089                action=ACTION_READ;
090                columnMap=null;
091                enclosureDir=null;
092                ignoreEnclosureError=false;
093                name=null;
094                outputFile=null;
095                overwrite=false;
096                overwriteEnclosure=false;
097                properties=null;
098                query=null;
099                source=null;
100                timeout=-1;
101                userAgent=null;
102                xmlVar=null;
103
104                proxyPassword=null;
105                proxyPort=80;
106                proxyServer=null;
107                proxyUser=null;
108                
109                type=TYPE_AUTO;
110                super.release();
111        }
112        /** set the value charset
113        *  Character set name for the file contents.
114        * @param charset value to set
115        **/
116        public void setCharset(String charset)  {
117                this.charset=charset.trim();
118        }
119        
120        /**
121         * @param action the action to set
122         * @throws ApplicationException 
123         */
124        public void setAction(String strAction) throws ApplicationException {
125
126                strAction=StringUtil.toLowerCase(strAction.trim());
127                if("read".equals(strAction))                            action=ACTION_READ;
128                else if("create".equals(strAction))                             action=ACTION_CREATE;
129                
130                else throw new ApplicationException("invalid action definition ["+strAction+"], valid action definitions are " +
131                                "[create,read]");
132        }
133        
134        public void setType(String strType) throws ApplicationException {
135
136                strType=StringUtil.toLowerCase(strType.trim());
137                if("rss".equals(strType))                               type=TYPE_RSS;
138                else if("atom".equals(strType))                 type=TYPE_ATOM;
139                
140                else throw new ApplicationException("invalid type definition ["+strType+"], valid type definitions are " +
141                                "[atom,rss]");
142        }
143        /**
144         * @param columnMap the columnMap to set
145         */
146        public void setColumnmap(Struct columnMap) {
147                this.columnMap = columnMap;
148        }
149        /**
150         * @param enclosureDir the enclosureDir to set
151         * @throws ExpressionException 
152         */
153        public void setEnclosuredir(String strEnclosureDir) throws ExpressionException {
154                this.enclosureDir = ResourceUtil.toResourceExisting(pageContext, strEnclosureDir);
155        }
156        /**
157         * @param ignoreEnclosureError the ignoreEnclosureError to set
158         */
159        public void setIgnoreenclosureerror(boolean ignoreEnclosureError) {
160                this.ignoreEnclosureError = ignoreEnclosureError;
161        }
162        /**
163         * @param name the name to set
164         */
165        public void setName(Object name) {
166                this.name = name;
167        }
168        /**
169         * @param outputFile the outputFile to set
170         * @throws ExpressionException 
171         */
172        public void setOutputfile(String strOutputFile) throws ExpressionException {
173                this.outputFile = ResourceUtil.toResourceExistingParent(pageContext, strOutputFile);
174        }
175        /**
176         * @param overwrite the overwrite to set
177         */
178        public void setOverwrite(boolean overwrite) {
179                this.overwrite = overwrite;
180        }
181        /**
182         * @param overwriteEnclosure the overwriteEnclosure to set
183         */
184        public void setOverwriteenclosure(boolean overwriteEnclosure) {
185                this.overwriteEnclosure = overwriteEnclosure;
186        }
187        /**
188         * @param properties the properties to set
189         */
190        public void setProperties(Object properties) {
191                this.properties = properties;
192        }
193        /**
194         * @param query the query to set
195         */
196        public void setQuery(Object query) {
197                this.query = query;
198        }
199        /**
200         * @param source the source to set
201         * @throws ExpressionException 
202         */
203        public void setSource(String strSource) throws ExpressionException {
204                // when using  toExistingResource execution fails because proxy is missed at this time
205                this.source = ResourceUtil.toResourceNotExisting(pageContext, strSource);
206        }
207        /**
208         * @param timeout the timeout to set
209         */
210        public void setTimeout(double timeout) {
211                this.timeout = (int) timeout;
212        }
213        /**
214         * @param userAgent the userAgent to set
215         */
216        public void setUseragent(String userAgent) {
217                this.userAgent = userAgent;
218        }
219        /**
220         * @param xmlVar the xmlVar to set
221         */
222        public void setXmlvar(String xmlVar) {
223                this.xmlVar = xmlVar;
224        }
225        /**
226         * @param proxyPassword the proxyPassword to set
227         */
228        public void setProxypassword(String proxyPassword) {
229                this.proxyPassword = proxyPassword;
230        }
231        
232        /**
233         * @param proxyPort the proxyPort to set
234         */
235        public void setProxyport(double proxyPort) {
236                this.proxyPort = (int) proxyPort;
237        }
238        
239        /**
240         * @param proxyServer the proxyServer to set
241         */
242        public void setProxyserver(String proxyServer) {
243                this.proxyServer = proxyServer;
244        }
245        /**
246         * @param proxyUser the proxyUser to set
247         */
248        public void setProxyuser(String proxyUser) {
249                this.proxyUser = proxyUser;
250        }
251        
252
253        
254        @Override
255        public int doStartTag() throws PageException {
256                if(source instanceof HTTPResource) {
257                        HTTPResource httpSource = (HTTPResource)source;
258                        if(!StringUtil.isEmpty(proxyServer,true)){
259                                ProxyData data=new ProxyDataImpl(proxyServer,proxyPort,proxyUser,proxyPassword);
260                                httpSource.setProxyData(data);
261                        }
262                        if(!StringUtil.isEmpty(userAgent))
263                                httpSource.setUserAgent(userAgent);
264                        if(timeout>-1)httpSource.setTimeout(timeout*1000);
265                }
266                
267                try {
268                        if(ACTION_CREATE==action)               doActionCreate();
269                        else if(ACTION_READ==action)    doActionRead();
270                }
271                catch(Exception e) {
272                        throw Caster.toPageException(e);
273                }
274                return SKIP_BODY;
275        }
276        
277        private void doActionCreate() throws PageException {
278                
279                // name
280                Query qry;
281                Struct props;
282                boolean splitString=true;
283                if(name!=null)  {
284                        Struct data;
285                        if(name instanceof String){
286                                data=Caster.toStruct(pageContext.getVariable(Caster.toString(name)));
287                        }
288                        else data = Caster.toStruct(name,false);
289                        
290                        qry = FeedQuery.toQuery(data,false);
291                        props = FeedProperties.toProperties(data);
292                        splitString=false;
293                }
294                else if(query!=null && properties!=null)        {
295                        qry = FeedQuery.toQuery(Caster.toQuery(query));
296                        props = FeedProperties.toProperties(Caster.toStruct(properties,false));
297                }
298                else {
299                        throw new ApplicationException("missing attribute [name] or attributes [query] and [properties]");
300                }
301                
302                StringBuffer xml=new StringBuffer("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
303                
304                if(type==TYPE_AUTO){
305                        String version=Caster.toString(props.get("version","rss"),"rss");
306                        type=StringUtil.startsWithIgnoreCase(version, "rss")?TYPE_RSS:TYPE_ATOM;
307                }
308                if(type==TYPE_RSS) {
309                        createRSS(xml,qry,props,splitString);
310                }
311                else {
312                        createAtom(xml, qry, props, splitString);
313                }
314                
315                // variable
316                if(!StringUtil.isEmpty(xmlVar)) {
317                        pageContext.setVariable(xmlVar, xml);
318                }
319                //file
320                if(outputFile!=null){
321                        if(outputFile.exists() && !overwrite)
322                                throw new ApplicationException("destiniation file ["+outputFile+"] already exist");
323                        
324                        
325
326                        if(StringUtil.isEmpty(charset)) charset=((PageContextImpl)pageContext).getResourceCharset().name();
327                        
328                        try {
329                                IOUtil.write(outputFile, xml.toString(),charset,false);
330                        } catch (IOException e) {
331                                throw Caster.toPageException(e);
332                        }
333                }
334                
335                
336                
337                
338                
339                /*
340<cffeed
341    action = "create"
342    name = "#structure#"
343        One or both of the following:
344    outputFile = "path"
345    xmlVar = "variable name"
346    optional 
347    overwrite = "no|yes">
348 
349    <cffeed
350    action = "create"
351    properties = "#metadata structure#"
352    query = "#items/entries query name#"
353        One or both of the following:
354    outputFile = "path"
355    xmlVar = "variable name"
356    optional
357    columnMap = "mapping structure"
358    overwrite = "no|yes"> 
359                 */
360                
361        }
362        
363        private void createAtom(StringBuffer xml, Query query, Struct props,boolean splitString) throws PageException {
364                int rows=query.getRowCount();
365                
366                append(xml,0,"<feed xmlns=\"http://www.w3.org/2005/Atom\">");
367
368                propTag(props,xml,1,new String[]{"title"},"title",
369                                new String[][]{
370                                        new String[]{"type","type"}
371                                }
372                );
373                propTag(props,xml,1,new String[]{"subtitle"},"subtitle",
374                                new String[][]{
375                                        new String[]{"type","type"}
376                                }
377                );
378                propTag(props,xml,1,new String[]{"updated"},"updated",null);
379                propTag(props,xml,1,new String[]{"id"},"id",null);
380                propTag(props,xml,1,new String[]{"link"},"link",
381                                new String[][]{
382                                new String[]{"rel","rel"},
383                                new String[]{"type","type"},
384                                new String[]{"hreflang","hreflang"},
385                                new String[]{"href","href"}
386                                }
387                );
388                propTag(props,xml,1,new String[]{"rights"},"rights",null);
389                propTag(props,xml,1,new String[]{"generator"},"generator",
390                                new String[][]{
391                                new String[]{"uri","uri"},
392                                new String[]{"version","version"}
393                                }
394                );
395                
396                // items
397                for(int row=1;row<=rows;row++) {
398                        append(xml,1,"<entry>");
399
400                        tag(xml,2,
401                                        new Pair<String,Object>("title",query.getAt(FeedQuery.TITLE, row, null)),
402                                        new Pair[]{
403                                                new Pair<String,Object>("type",query.getAt(FeedQuery.TITLETYPE, row, null))
404                                        },false,splitString
405                        );
406                        tag(xml,2,
407                                        new Pair<String,Object>("link",null),
408                                        new Pair[]{
409                                                new Pair<String,Object>("href",query.getAt(FeedQuery.LINKHREF, row, null)),
410                                                new Pair<String,Object>("hreflang",query.getAt(FeedQuery.LINKHREFLANG, row, null)),
411                                                new Pair<String,Object>("length",query.getAt(FeedQuery.LINKLENGTH, row, null)),
412                                                new Pair<String,Object>("rel",query.getAt(FeedQuery.LINKREL, row, null)),
413                                                new Pair<String,Object>("title",query.getAt(FeedQuery.LINKTITLE, row, null)),
414                                                new Pair<String,Object>("type",query.getAt(FeedQuery.LINKTYPE, row, null))
415                                        },false,splitString
416                        );
417                        tag(xml,2,new Pair<String,Object>("id",query.getAt(FeedQuery.ID, row, null)),null,true,false);
418                        tag(xml,2,new Pair<String,Object>("updated",query.getAt(FeedQuery.UPDATEDDATE, row, null)),null,true,false);
419                        tag(xml,2,new Pair<String,Object>("published",query.getAt(FeedQuery.PUBLISHEDDATE, row, null)),null,true,false);
420                        tag(xml,2,
421                                        new Pair<String,Object>("author",null),
422                                        new Pair[]{
423                                                new Pair<String,Object>("email",query.getAt(FeedQuery.AUTHOREMAIL, row, null)),
424                                                new Pair<String,Object>("name",query.getAt(FeedQuery.AUTHORNAME, row, null)),
425                                                new Pair<String,Object>("uri",query.getAt(FeedQuery.AUTHORURI, row, null))
426                                        },false,splitString
427                        );
428                        tag(xml,2,
429                                        new Pair<String,Object>("category",null),
430                                        new Pair[]{
431                                                new Pair<String,Object>("label",query.getAt(FeedQuery.CATEGORYLABEL, row, null)),
432                                                new Pair<String,Object>("scheme",query.getAt(FeedQuery.CATEGORYSCHEME, row, null)),
433                                                new Pair<String,Object>("term",query.getAt(FeedQuery.CATEGORYTERM, row, null)),
434                                        },false,splitString
435                        );
436                        tag(xml,2,
437                                        new Pair<String,Object>("contributor",null),
438                                        new Pair[]{
439                                                new Pair<String,Object>("email",query.getAt(FeedQuery.CONTRIBUTOREMAIL, row, null)),
440                                                new Pair<String,Object>("name",query.getAt(FeedQuery.CONTRIBUTORNAME, row, null)),
441                                                new Pair<String,Object>("uri",query.getAt(FeedQuery.CONTRIBUTORURI, row, null))
442                                        },false,splitString
443                        );
444                        tag(xml,2,
445                                        new Pair<String,Object>("content",query.getAt(FeedQuery.CONTENT, row, null)),
446                                        new Pair[]{
447                                                new Pair<String,Object>("src",query.getAt(FeedQuery.CONTENTSRC, row, null)),
448                                                new Pair<String,Object>("type",query.getAt(FeedQuery.CONTENTTYPE, row, null)),
449                                                new Pair<String,Object>("type",query.getAt(FeedQuery.CONTENTTYPE, row, null))
450                                        },false,splitString
451                        );
452                        tag(xml,2,new Pair<String,Object>("rights",query.getAt(FeedQuery.RIGHTS, row, null)),null,true,false);
453                        tag(xml,2,
454                                        new Pair<String,Object>("summary",query.getAt(FeedQuery.SUMMARY, row, null)),
455                                        new Pair[]{
456                                                new Pair<String,Object>("type",query.getAt(FeedQuery.SUMMARYTYPE, row, null))
457                                        },false,splitString
458                        );
459                        
460                        append(xml,1,"</entry>");
461                }
462                
463                
464                
465                
466                append(xml,0,"</feed>");
467                
468        }
469        private void createRSS(StringBuffer xml, Query query, Struct props, boolean splitString) throws PageException {
470                int rows=query.getRowCount();
471                
472                append(xml,0,"<rss xmlns:taxo=\"http://purl.org/rss/1.0/modules/taxonomy/\" xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\" xmlns:itunes=\"http://www.itunes.com/dtds/podcast-1.0.dtd\" xmlns:dc=\"http://purl.org/dc/elements/1.1/\" version=\"2.0\">");
473                append(xml,1,"<channel>");
474                
475                // title
476                propTag(props,xml,2,new String[]{"title"},"title",null);
477                propTag(props,xml,2,new String[]{"link"},"link",null);
478                propTag(props,xml,2,new String[]{"description","subtitle"},"description",null);
479                propTag(props,xml,2,new String[]{"language"},"language",null);
480                propTag(props,xml,2,new String[]{"copyright"},"copyright",null);
481                propTag(props,xml,2,new String[]{"managingEditor"},"managingEditor",null);
482                propTag(props,xml,2,new String[]{"webMaster"},"webMaster",null);
483                propTag(props,xml,2,new String[]{"pubDate"},"pubDate",null);
484                propTag(props,xml,2,new String[]{"lastBuildDate"},"lastBuildDate",null);
485                propTag(props,xml,2,new String[]{"category"},"category",
486                                new String[][]{
487                                        new String[]{"domain","domain"}
488                                }
489                );
490                propTag(props,xml,2,new String[]{"generator"},"generator",null);
491                propTag(props,xml,2,new String[]{"docs"},"docs",null);
492                propTag(props,xml,2,new String[]{"cloud"},"cloud",
493                                new String[][]{
494                                new String[]{"domain","domain"},
495                                new String[]{"port","port"},
496                                new String[]{"path","path"},
497                                new String[]{"registerProcedure","registerProcedure"},
498                                new String[]{"protocol","protocol"}
499                                }
500                );
501                propTag(props,xml,2,new String[]{"ttl"},"ttl",null);
502                propTag(props,xml,2,new String[]{"image"},"image",
503                                new String[][]{
504                                new String[]{"url","url"},
505                                new String[]{"title","title"},
506                                new String[]{"link","link"},
507                                new String[]{"width","width"},
508                                new String[]{"height","height"},
509                                new String[]{"description","description"}
510                                }
511                ,true);
512                propTag(props,xml,2,new String[]{"textInput"},"textInput",
513                                new String[][]{
514                                new String[]{"title","title"},
515                                new String[]{"description","description"},
516                                new String[]{"name","name"},
517                                new String[]{"link","link"}
518                                }
519                ,true);
520                propTag(props,xml,2,new String[]{"skipHours"},"skipHours",null);
521                propTag(props,xml,2,new String[]{"skipDays"},"skipDays",null);
522                
523                // items
524                for(int row=1;row<=rows;row++) {
525                        append(xml,2,"<item>");
526                        
527                        tag(xml,3,new Pair<String,Object>("title",(query.getAt(FeedQuery.TITLE, row, null))),null,true,false);
528                        tag(xml,3,new Pair<String,Object>("description",(query.getAt(FeedQuery.CONTENT, row, null))),null,true,false);
529                        tag(xml,3,new Pair<String,Object>("link",query.getAt(FeedQuery.RSSLINK, row, null)),null,false,false);
530                        tag(xml,3,new Pair<String,Object>("author",query.getAt(FeedQuery.AUTHOREMAIL, row, null)),null,false,false);
531                        tag(xml,3,
532                                        new Pair<String,Object>("category",query.getAt(FeedQuery.CATEGORYLABEL, row, null)),
533                                        new Pair[]{
534                                                new Pair<String,Object>("domain",query.getAt(FeedQuery.CATEGORYSCHEME, row, null))
535                                        },false,splitString
536                        );
537                        tag(xml,3,new Pair<String,Object>("comments",query.getAt(FeedQuery.COMMENTS, row, null)),null,false,false);
538                        tag(xml,3,
539                                        new Pair<String,Object>("enclosure",null),
540                                        new Pair[]{
541                                                new Pair<String,Object>("url",query.getAt(FeedQuery.LINKHREF, row, null)),
542                                                new Pair<String,Object>("length",query.getAt(FeedQuery.LINKLENGTH, row, null)),
543                                                new Pair<String,Object>("type",query.getAt(FeedQuery.LINKTYPE, row, null))
544                                        },false,splitString
545                        );
546                        tag(xml,3,
547                                        new Pair<String,Object>("guid",query.getAt(FeedQuery.ID, row, null)),
548                                        new Pair[]{
549                                                new Pair<String,Object>("isPermaLink",query.getAt(FeedQuery.IDPERMALINK, row, null))
550                                        },false,splitString
551                        );
552                        tag(xml,3,new Pair<String,Object>("pubDate",query.getAt(FeedQuery.PUBLISHEDDATE, row, null)),null,false,splitString);
553                        tag(xml,3,
554                                        new Pair<String,Object>("source",query.getAt(FeedQuery.SOURCE, row, null)),
555                                        new Pair[]{
556                                                new Pair<String,Object>("url",query.getAt(FeedQuery.SOURCEURL, row, null))
557                                        },false,false
558                        );
559                        
560                        
561                        
562                        
563                        
564                        append(xml,2,"</item>");
565                }
566                
567                append(xml,1,"</channel>");
568                append(xml,0,"</rss>");
569                
570        }
571        
572        private void propTag(Struct props,StringBuffer xml, int count,String[] srcNames,String trgName,String[][] attrNames) throws PageException {
573                propTag(props, xml, count, srcNames, trgName, attrNames,false);
574        }
575                
576        
577        private void propTag(Struct props,StringBuffer xml, int count,String[] srcNames,String trgName,String[][] attrNames, boolean childrenAsTag) throws PageException {
578                Object value;
579                for(int i=0;i<srcNames.length;i++) {
580                        value=props.get(srcNames[i], null);
581                        
582                        if(value instanceof Array){
583                                Array arr = (Array)value;
584                                int size = arr.size();
585                                for(int y=1;y<=size;y++) {
586                                        propTag(xml, count, arr.get(y, null), trgName, attrNames, childrenAsTag);
587                                }
588                                break;
589                        }
590                        if(value !=null)        {
591                                propTag(xml, count, value, trgName, attrNames, childrenAsTag);
592                                break;
593                        }
594                }
595        }
596        
597        private boolean propTag(StringBuffer xml, int count,Object value,String trgName,String[][] attrNames, boolean childrenAsTag) throws PageException {
598                if(!StringUtil.isEmpty(value)){
599                        Pair[] attrs;
600                        if(value instanceof Struct && attrNames!=null) {
601                                Struct sct = (Struct)value;
602                                Object attrValue;
603                                ArrayList al=new ArrayList();
604                                for(int i=0;i<attrNames.length;i++) {
605                                        attrValue=sct.get(attrNames[i][0], null);
606                                        if(attrValue!=null) {
607                                                al.add(new Pair<String,Object>(attrNames[i][1],attrValue));
608                                        }
609                                }
610                                attrs=(Pair[]) al.toArray(new Pair[al.size()]);
611                        }
612                        else attrs=null;
613                        tag(xml,count,new Pair<String,Object>(trgName,FeedQuery.getValue(value)),attrs,false,false,childrenAsTag);
614                        return true;
615                }
616                return false;
617                
618        }
619        private void tag(StringBuffer xml, int count, Pair<String,Object> tag, Pair<String,Object>[] attrs,boolean required,boolean splitString) throws PageException {
620                tag(xml, count, tag, attrs, required, splitString,false);
621        }
622        
623        private void tag(StringBuffer xml, int count, Pair<String,Object> tag, Pair<String,Object>[] attrs,boolean required,boolean splitString, boolean childrenAsTag) throws PageException {
624                if(!required && StringUtil.isEmpty(tag.getValue())){
625                        if(attrs==null || attrs.length==0)return;
626                        int c=0;
627                        for(int i=0;i<attrs.length;i++){
628                                if(!StringUtil.isEmpty(attrs[i].getValue())) c++;
629                                
630                        }
631                        if(c==0)return;
632                }
633                
634                if(tag.getValue() instanceof Array){
635                        Array arr=(Array) tag.getValue();
636                        int len=arr.size();
637                        for(int i=1;i<=len;i++) {
638                                _tag(xml, tag.getName(),arr.get(i, null), attrs, count, i,false,childrenAsTag);
639                        }
640                        return;
641                        
642                }
643                if(splitString && tag.getValue() instanceof String)     {
644                        String strValue=(String) tag.getValue();
645                        Array arr = ListUtil.listToArray(strValue, ',');
646                        if(arr.size()>1){
647                                int len=arr.size();
648                                for(int i=1;i<=len;i++) {
649                                        _tag(xml, tag.getName(),arr.get(i, null), attrs, count, i,true,childrenAsTag);
650                                }
651                                return;
652                        }
653                }
654                _tag(xml, tag.getName(), tag.getValue(), attrs, count, 0,false,childrenAsTag);
655                
656                
657        }
658        
659        private void _tag(StringBuffer xml, String tagName,Object tagValue, Pair<String,Object>[] attrs,int count, int index, boolean splitString, boolean childrenAsTag) throws PageException {
660                for(int i=0;i<count;i++)xml.append("\t");
661                xml.append('<');
662                xml.append(tagName);
663                
664                Object attrValue;
665                if(attrs!=null && !childrenAsTag){
666                        for(int i=0;i<attrs.length;i++){
667                                attrValue=attrs[i].getValue();
668                                if(index>0){
669                                        if(attrValue instanceof Array)attrValue=((Array)attrValue).get(index,null);
670                                        else if(splitString && attrValue instanceof String){
671                                                Array arr = ListUtil.listToArray((String)attrValue, ',');
672                                                attrValue=arr.get(index,null);
673                                        }
674                                }
675                                if(StringUtil.isEmpty(attrValue)) continue;
676                                
677                                xml.append(' ');
678                                xml.append(attrs[i].getName());
679                                xml.append("=\"");
680                                xml.append(XMLUtil.escapeXMLString(toString(attrValue)));
681                                xml.append("\"");
682                        }
683                }
684                
685                xml.append('>');
686                        xml.append(toString(tagValue));
687                        if(attrs!=null && attrs.length>0 && childrenAsTag){
688                                xml.append('\n');
689                                for(int i=0;i<attrs.length;i++){
690                                        attrValue=attrs[i].getValue();
691                                        if(index>0){
692                                                if(attrValue instanceof Array)attrValue=((Array)attrValue).get(index,null);
693                                                else if(splitString && attrValue instanceof String){
694                                                        Array arr = ListUtil.listToArray((String)attrValue, ',');
695                                                        attrValue=arr.get(index,null);
696                                                }
697                                        }
698                                        if(StringUtil.isEmpty(attrValue)) continue;
699
700                                        for(int y=0;y<count+1;y++)xml.append("\t");
701                                        xml.append('<');
702                                        xml.append(attrs[i].getName());
703                                        xml.append('>');
704                                        //xml.append(XMLUtil.escapeXMLString(toString(attrValue)));
705                                        xml.append(toString(attrValue));
706                                        xml.append("</");
707                                        xml.append(attrs[i].getName());
708                                        xml.append(">\n");
709                                }
710                                for(int y=0;y<count;y++)xml.append("\t");
711                        }
712                        
713                        
714                        
715                        
716                xml.append("</");
717                xml.append(tagName);
718                xml.append(">\n");   
719
720        }
721        
722        
723        private String toString(Object value) throws PageException {
724                if(Decision.isDateAdvanced(value, false)) return GetHttpTimeString.invoke(Caster.toDatetime(value, pageContext.getTimeZone()));
725                 return XMLUtil.escapeXMLString(Caster.toString(value));
726        }
727        
728        private static void append(StringBuffer xml, int count, String value) {
729                for(int i=0;i<count;i++)xml.append("\t");
730                xml.append(value);
731                xml.append("\n");
732        }
733        
734        private void doActionRead() throws IOException, SAXException, PageException {
735                required("Feed", "read", "source", source);
736
737                if(outputFile!=null && outputFile.exists() && !overwrite)
738                        throw new ApplicationException("outputFile file ["+outputFile+"] already exists");
739                
740                
741                String charset=null;
742                
743// plain output
744                // xmlVar
745                if(outputFile!=null) {
746                        IOUtil.copy(source, outputFile);
747                }
748                // outputFile
749                String strFeed=null;
750                if(!StringUtil.isEmpty(xmlVar)) {
751                        strFeed = IOUtil.toString(outputFile!=null?outputFile:source,charset);
752                        pageContext.setVariable(xmlVar, strFeed);
753                }
754                // Input Source
755                InputSource is=null;
756                Reader r=null;
757                if(strFeed!=null)is=new InputSource(new StringReader(strFeed));
758                else if(outputFile!=null)is=new InputSource(r=IOUtil.getReader(outputFile, charset));
759                else is=new InputSource(r=IOUtil.getReader(source, charset));
760                is.setSystemId(source.getPath());
761                
762                try{
763                        FeedHandler feed=new FeedHandler(source);
764                        Struct data = feed.getData();
765                        //print.e(data.keys());
766                        //print.e(data);
767                        // properties
768                        if(properties!=null) {
769                                String strProp = Caster.toString(properties,null);
770                                if(strProp==null)throw new ApplicationException("attribute [properties] should be of type string");
771                                pageContext.setVariable(strProp, FeedProperties.toProperties(data));
772                        }
773                        
774                        // query or enclosure
775                        lucee.runtime.type.Query qry=null;
776                        if(query!=null || enclosureDir!=null){
777                                qry=FeedQuery.toQuery(data,feed.hasDC());
778                        }
779                        
780                        // query
781                        if(query!=null) {
782                                String strQuery = Caster.toString(query,null);
783                                if(strQuery==null)throw new ApplicationException("attribute [query] should be of type string");
784                                pageContext.setVariable(strQuery, qry);
785                        }
786                        // enclosure
787                        
788                        if(enclosureDir!=null) {
789                                int rows=qry.getRowCount();
790                                String strUrl=null;
791                                Resource src,dest;
792                                for(int row=1;row<=rows;row++) {
793                                        strUrl=Caster.toString(qry.getAt(FeedQuery.LINKHREF, row, null),null);
794                                        if(!StringUtil.isEmpty(strUrl)){
795                                                src=ResourceUtil.toResourceNotExisting(pageContext, strUrl);
796                                                dest=enclosureDir.getRealResource(src.getName());
797                                                if(!ignoreEnclosureError && !overwriteEnclosure && dest.exists())
798                                                        throw new ApplicationException("enclosure file ["+dest+"] already exists");
799                                                try {
800                                                IOUtil.copy(src, dest);
801                                                }
802                                                catch(IOException ioe) {
803                                                        if(!ignoreEnclosureError)throw ioe;
804                                                }
805                                        }
806                                }
807                        }
808                        
809                        // name
810                        if(name!=null) {
811                                String strName = Caster.toString(name,null);
812                                if(strName==null)throw new ApplicationException("attribute [name] should be of type string");
813                                pageContext.setVariable(strName, data);
814                        }
815                        
816                        
817                        
818                }
819                finally {
820                        IOUtil.closeEL(r);
821                }
822        }
823}