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