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