package railo.runtime.tag;

import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;

import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import railo.commons.io.IOUtil;
import railo.commons.io.res.Resource;
import railo.commons.io.res.type.http.HTTPResource;
import railo.commons.io.res.util.ResourceUtil;
import railo.commons.lang.Pair;
import railo.commons.lang.StringUtil;
import railo.runtime.exp.ApplicationException;
import railo.runtime.exp.ExpressionException;
import railo.runtime.exp.PageException;
import railo.runtime.ext.tag.TagImpl;
import railo.runtime.functions.dateTime.GetHttpTimeString;
import railo.runtime.net.proxy.ProxyData;
import railo.runtime.net.proxy.ProxyDataImpl;
import railo.runtime.op.Caster;
import railo.runtime.op.Decision;
import railo.runtime.text.feed.FeedHandler;
import railo.runtime.text.feed.FeedQuery;
import railo.runtime.text.xml.XMLUtil;
import railo.runtime.type.Array;
import railo.runtime.type.Query;
import railo.runtime.type.Struct;
import railo.runtime.type.util.ListUtil;

public final class Feed extends TagImpl	{

	private static final int ACTION_READ = 0;
	private static final int ACTION_CREATE = 1;
	
	private static final int TYPE_AUTO = 0;
	private static final int TYPE_RSS = 1;
	private static final int TYPE_ATOM = 2;
	
	
	
	private int action=ACTION_READ;
	private Struct columnMap=null;
	private Resource enclosureDir=null;
	private boolean ignoreEnclosureError=false;
	private Object name=null;
	private Resource outputFile=null;
	private boolean overwrite=false;
	private boolean overwriteEnclosure=false;
	private Object properties=null;
	private Object query=null;
	private Resource source=null;
	private int timeout=-1;
	private int type=TYPE_AUTO;
	private String userAgent=null;
	private String xmlVar=null;

	private String proxyPassword=null;
	private int proxyPort=80;
	private String proxyServer=null;
	private String proxyUser=null;
	private String charset=null;
	
	@Override
	public void release() {
		charset=null;
		action=ACTION_READ;
		columnMap=null;
		enclosureDir=null;
		ignoreEnclosureError=false;
		name=null;
		outputFile=null;
		overwrite=false;
		overwriteEnclosure=false;
		properties=null;
		query=null;
		source=null;
		timeout=-1;
		userAgent=null;
		xmlVar=null;

		proxyPassword=null;
		proxyPort=80;
		proxyServer=null;
		proxyUser=null;
		
		type=TYPE_AUTO;
		super.release();
	}
	/** set the value charset
	*  Character set name for the file contents.
	* @param charset value to set
	**/
	public void setCharset(String charset)	{
		this.charset=charset.trim();
	}
	
	/**
	 * @param action the action to set
	 * @throws ApplicationException 
	 */
	public void setAction(String strAction) throws ApplicationException {

		strAction=StringUtil.toLowerCase(strAction.trim());
		if("read".equals(strAction))				action=ACTION_READ;
		else if("create".equals(strAction))				action=ACTION_CREATE;
		
		else throw new ApplicationException("invalid action definition ["+strAction+"], valid action definitions are " +
				"[create,read]");
	}
	
	public void setType(String strType) throws ApplicationException {

		strType=StringUtil.toLowerCase(strType.trim());
		if("rss".equals(strType))				type=TYPE_RSS;
		else if("atom".equals(strType))			type=TYPE_ATOM;
		
		else throw new ApplicationException("invalid type definition ["+strType+"], valid type definitions are " +
				"[atom,rss]");
	}
	/**
	 * @param columnMap the columnMap to set
	 */
	public void setColumnmap(Struct columnMap) {
		this.columnMap = columnMap;
	}
	/**
	 * @param enclosureDir the enclosureDir to set
	 * @throws ExpressionException 
	 */
	public void setEnclosuredir(String strEnclosureDir) throws ExpressionException {
		this.enclosureDir = ResourceUtil.toResourceExisting(pageContext, strEnclosureDir);
	}
	/**
	 * @param ignoreEnclosureError the ignoreEnclosureError to set
	 */
	public void setIgnoreenclosureerror(boolean ignoreEnclosureError) {
		this.ignoreEnclosureError = ignoreEnclosureError;
	}
	/**
	 * @param name the name to set
	 */
	public void setName(Object name) {
		this.name = name;
	}
	/**
	 * @param outputFile the outputFile to set
	 * @throws ExpressionException 
	 */
	public void setOutputfile(String strOutputFile) throws ExpressionException {
		this.outputFile = ResourceUtil.toResourceExistingParent(pageContext, strOutputFile);
	}
	/**
	 * @param overwrite the overwrite to set
	 */
	public void setOverwrite(boolean overwrite) {
		this.overwrite = overwrite;
	}
	/**
	 * @param overwriteEnclosure the overwriteEnclosure to set
	 */
	public void setOverwriteenclosure(boolean overwriteEnclosure) {
		this.overwriteEnclosure = overwriteEnclosure;
	}
	/**
	 * @param properties the properties to set
	 */
	public void setProperties(Object properties) {
		this.properties = properties;
	}
	/**
	 * @param query the query to set
	 */
	public void setQuery(Object query) {
		this.query = query;
	}
	/**
	 * @param source the source to set
	 * @throws ExpressionException 
	 */
	public void setSource(String strSource) throws ExpressionException {
		// when using  toExistingResource execution fails because proxy is missed at this time
		this.source = ResourceUtil.toResourceNotExisting(pageContext, strSource);
	}
	/**
	 * @param timeout the timeout to set
	 */
	public void setTimeout(double timeout) {
		this.timeout = (int) timeout;
	}
	/**
	 * @param userAgent the userAgent to set
	 */
	public void setUseragent(String userAgent) {
		this.userAgent = userAgent;
	}
	/**
	 * @param xmlVar the xmlVar to set
	 */
	public void setXmlvar(String xmlVar) {
		this.xmlVar = xmlVar;
	}
	/**
	 * @param proxyPassword the proxyPassword to set
	 */
	public void setProxypassword(String proxyPassword) {
		this.proxyPassword = proxyPassword;
	}
	
	/**
	 * @param proxyPort the proxyPort to set
	 */
	public void setProxyport(double proxyPort) {
		this.proxyPort = (int) proxyPort;
	}
	
	/**
	 * @param proxyServer the proxyServer to set
	 */
	public void setProxyserver(String proxyServer) {
		this.proxyServer = proxyServer;
	}
	/**
	 * @param proxyUser the proxyUser to set
	 */
	public void setProxyuser(String proxyUser) {
		this.proxyUser = proxyUser;
	}
	

	
	@Override
	public int doStartTag() throws PageException {
		if(source instanceof HTTPResource) {
			HTTPResource httpSource = (HTTPResource)source;
			if(!StringUtil.isEmpty(proxyServer,true)){
				ProxyData data=new ProxyDataImpl(proxyServer,proxyPort,proxyUser,proxyPassword);
				httpSource.setProxyData(data);
			}
			if(!StringUtil.isEmpty(userAgent))
				httpSource.setUserAgent(userAgent);
			if(timeout>-1)httpSource.setTimeout(timeout*1000);
		}
		
		try {
			if(ACTION_CREATE==action)		doActionCreate();
			else if(ACTION_READ==action)	doActionRead();
		}
		catch(Exception e) {
			throw Caster.toPageException(e);
		}
		return SKIP_BODY;
	}
	
	private void doActionCreate() throws PageException {
		
		// name
		Query qry;
		Struct props;
		boolean splitString=true;
		if(name!=null)	{
			Struct data;
			if(name instanceof String){
				data=Caster.toStruct(pageContext.getVariable(Caster.toString(name)));
			}
			else data = Caster.toStruct(name,false);
			
			qry = FeedQuery.toQuery(data,false);
			props = FeedProperties.toProperties(data);
			splitString=false;
		}
		else if(query!=null && properties!=null)	{
			qry = FeedQuery.toQuery(Caster.toQuery(query));
			props = FeedProperties.toProperties(Caster.toStruct(properties,false));
		}
		else {
			throw new ApplicationException("missing attribute [name] or attributes [query] and [properties]");
		}
		
		StringBuffer xml=new StringBuffer("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
		
		if(type==TYPE_AUTO){
			String version=Caster.toString(props.get("version","rss"),"rss");
			type=StringUtil.startsWithIgnoreCase(version, "rss")?TYPE_RSS:TYPE_ATOM;
		}
		if(type==TYPE_RSS) {
			createRSS(xml,qry,props,splitString);
		}
		else {
			createAtom(xml, qry, props, splitString);
		}
		
		// variable
		if(!StringUtil.isEmpty(xmlVar)) {
			pageContext.setVariable(xmlVar, xml);
		}
		//file
		if(outputFile!=null){
			if(outputFile.exists() && !overwrite)
				throw new ApplicationException("destiniation file ["+outputFile+"] already exist");
			
			

			if(StringUtil.isEmpty(charset)) charset=pageContext.getConfig().getResourceCharset();
			
			try {
				IOUtil.write(outputFile, xml.toString(),charset,false);
			} catch (IOException e) {
				throw Caster.toPageException(e);
			}
		}
		
		
		
		
		
		/*
<cffeed
    action = "create"
    name = "#structure#"
        One or both of the following:
    outputFile = "path"
    xmlVar = "variable name"
    optional 
    overwrite = "no|yes">
 
    <cffeed
    action = "create"
    properties = "#metadata structure#"
    query = "#items/entries query name#"
        One or both of the following:
    outputFile = "path"
    xmlVar = "variable name"
    optional
    columnMap = "mapping structure"
    overwrite = "no|yes"> 
		 */
		
	}
	
	private void createAtom(StringBuffer xml, Query query, Struct props,boolean splitString) throws PageException {
		int rows=query.getRowCount();
		
		append(xml,0,"<feed xmlns=\"http://www.w3.org/2005/Atom\">");

		propTag(props,xml,1,new String[]{"title"},"title",
				new String[][]{
					new String[]{"type","type"}
				}
		);
		propTag(props,xml,1,new String[]{"subtitle"},"subtitle",
				new String[][]{
					new String[]{"type","type"}
				}
		);
		propTag(props,xml,1,new String[]{"updated"},"updated",null);
		propTag(props,xml,1,new String[]{"id"},"id",null);
		propTag(props,xml,1,new String[]{"link"},"link",
				new String[][]{
				new String[]{"rel","rel"},
				new String[]{"type","type"},
				new String[]{"hreflang","hreflang"},
				new String[]{"href","href"}
				}
		);
		propTag(props,xml,1,new String[]{"rights"},"rights",null);
		propTag(props,xml,1,new String[]{"generator"},"generator",
				new String[][]{
				new String[]{"uri","uri"},
				new String[]{"version","version"}
				}
		);
		
		// items
		for(int row=1;row<=rows;row++) {
			append(xml,1,"<entry>");

			tag(xml,2,
					new Pair<String,Object>("title",query.getAt(FeedQuery.TITLE, row, null)),
					new Pair[]{
						new Pair<String,Object>("type",query.getAt(FeedQuery.TITLETYPE, row, null))
					},false,splitString
			);
			tag(xml,2,
					new Pair<String,Object>("link",null),
					new Pair[]{
						new Pair<String,Object>("href",query.getAt(FeedQuery.LINKHREF, row, null)),
						new Pair<String,Object>("hreflang",query.getAt(FeedQuery.LINKHREFLANG, row, null)),
						new Pair<String,Object>("length",query.getAt(FeedQuery.LINKLENGTH, row, null)),
						new Pair<String,Object>("rel",query.getAt(FeedQuery.LINKREL, row, null)),
						new Pair<String,Object>("title",query.getAt(FeedQuery.LINKTITLE, row, null)),
						new Pair<String,Object>("type",query.getAt(FeedQuery.LINKTYPE, row, null))
					},false,splitString
			);
			tag(xml,2,new Pair<String,Object>("id",query.getAt(FeedQuery.ID, row, null)),null,true,false);
			tag(xml,2,new Pair<String,Object>("updated",query.getAt(FeedQuery.UPDATEDDATE, row, null)),null,true,false);
			tag(xml,2,new Pair<String,Object>("published",query.getAt(FeedQuery.PUBLISHEDDATE, row, null)),null,true,false);
			tag(xml,2,
					new Pair<String,Object>("author",null),
					new Pair[]{
						new Pair<String,Object>("email",query.getAt(FeedQuery.AUTHOREMAIL, row, null)),
						new Pair<String,Object>("name",query.getAt(FeedQuery.AUTHORNAME, row, null)),
						new Pair<String,Object>("uri",query.getAt(FeedQuery.AUTHORURI, row, null))
					},false,splitString
			);
			tag(xml,2,
					new Pair<String,Object>("category",null),
					new Pair[]{
						new Pair<String,Object>("label",query.getAt(FeedQuery.CATEGORYLABEL, row, null)),
						new Pair<String,Object>("scheme",query.getAt(FeedQuery.CATEGORYSCHEME, row, null)),
						new Pair<String,Object>("term",query.getAt(FeedQuery.CATEGORYTERM, row, null)),
					},false,splitString
			);
			tag(xml,2,
					new Pair<String,Object>("contributor",null),
					new Pair[]{
						new Pair<String,Object>("email",query.getAt(FeedQuery.CONTRIBUTOREMAIL, row, null)),
						new Pair<String,Object>("name",query.getAt(FeedQuery.CONTRIBUTORNAME, row, null)),
						new Pair<String,Object>("uri",query.getAt(FeedQuery.CONTRIBUTORURI, row, null))
					},false,splitString
			);
			tag(xml,2,
					new Pair<String,Object>("content",query.getAt(FeedQuery.CONTENT, row, null)),
					new Pair[]{
						new Pair<String,Object>("src",query.getAt(FeedQuery.CONTENTSRC, row, null)),
						new Pair<String,Object>("type",query.getAt(FeedQuery.CONTENTTYPE, row, null)),
						new Pair<String,Object>("type",query.getAt(FeedQuery.CONTENTTYPE, row, null))
					},false,splitString
			);
			tag(xml,2,new Pair<String,Object>("rights",query.getAt(FeedQuery.RIGHTS, row, null)),null,true,false);
			tag(xml,2,
					new Pair<String,Object>("summary",query.getAt(FeedQuery.SUMMARY, row, null)),
					new Pair[]{
						new Pair<String,Object>("type",query.getAt(FeedQuery.SUMMARYTYPE, row, null))
					},false,splitString
			);
			
			append(xml,1,"</entry>");
		}
		
		
		
		
		append(xml,0,"</feed>");
		
	}
	private void createRSS(StringBuffer xml, Query query, Struct props, boolean splitString) throws PageException {
		int rows=query.getRowCount();
		
		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\">");
		append(xml,1,"<channel>");
		
		// title
		propTag(props,xml,2,new String[]{"title"},"title",null);
		propTag(props,xml,2,new String[]{"link"},"link",null);
		propTag(props,xml,2,new String[]{"description","subtitle"},"description",null);
		propTag(props,xml,2,new String[]{"language"},"language",null);
		propTag(props,xml,2,new String[]{"copyright"},"copyright",null);
		propTag(props,xml,2,new String[]{"managingEditor"},"managingEditor",null);
		propTag(props,xml,2,new String[]{"webMaster"},"webMaster",null);
		propTag(props,xml,2,new String[]{"pubDate"},"pubDate",null);
		propTag(props,xml,2,new String[]{"lastBuildDate"},"lastBuildDate",null);
		propTag(props,xml,2,new String[]{"category"},"category",
				new String[][]{
					new String[]{"domain","domain"}
				}
		);
		propTag(props,xml,2,new String[]{"generator"},"generator",null);
		propTag(props,xml,2,new String[]{"docs"},"docs",null);
		propTag(props,xml,2,new String[]{"cloud"},"cloud",
				new String[][]{
				new String[]{"domain","domain"},
				new String[]{"port","port"},
				new String[]{"path","path"},
				new String[]{"registerProcedure","registerProcedure"},
				new String[]{"protocol","protocol"}
				}
		);
		propTag(props,xml,2,new String[]{"ttl"},"ttl",null);
		propTag(props,xml,2,new String[]{"image"},"image",
				new String[][]{
				new String[]{"url","url"},
				new String[]{"title","title"},
				new String[]{"link","link"},
				new String[]{"width","width"},
				new String[]{"height","height"},
				new String[]{"description","description"}
				}
		,true);
		propTag(props,xml,2,new String[]{"textInput"},"textInput",
				new String[][]{
				new String[]{"title","title"},
				new String[]{"description","description"},
				new String[]{"name","name"},
				new String[]{"link","link"}
				}
		,true);
		propTag(props,xml,2,new String[]{"skipHours"},"skipHours",null);
		propTag(props,xml,2,new String[]{"skipDays"},"skipDays",null);
		
		// items
		for(int row=1;row<=rows;row++) {
			append(xml,2,"<item>");
			
			tag(xml,3,new Pair<String,Object>("title",(query.getAt(FeedQuery.TITLE, row, null))),null,true,false);
			tag(xml,3,new Pair<String,Object>("description",(query.getAt(FeedQuery.CONTENT, row, null))),null,true,false);
			tag(xml,3,new Pair<String,Object>("link",query.getAt(FeedQuery.RSSLINK, row, null)),null,false,false);
			tag(xml,3,new Pair<String,Object>("author",query.getAt(FeedQuery.AUTHOREMAIL, row, null)),null,false,false);
			tag(xml,3,
					new Pair<String,Object>("category",query.getAt(FeedQuery.CATEGORYLABEL, row, null)),
					new Pair[]{
						new Pair<String,Object>("domain",query.getAt(FeedQuery.CATEGORYSCHEME, row, null))
					},false,splitString
			);
			tag(xml,3,new Pair<String,Object>("comments",query.getAt(FeedQuery.COMMENTS, row, null)),null,false,false);
			tag(xml,3,
					new Pair<String,Object>("enclosure",null),
					new Pair[]{
						new Pair<String,Object>("url",query.getAt(FeedQuery.LINKHREF, row, null)),
						new Pair<String,Object>("length",query.getAt(FeedQuery.LINKLENGTH, row, null)),
						new Pair<String,Object>("type",query.getAt(FeedQuery.LINKTYPE, row, null))
					},false,splitString
			);
			tag(xml,3,
					new Pair<String,Object>("guid",query.getAt(FeedQuery.ID, row, null)),
					new Pair[]{
						new Pair<String,Object>("isPermaLink",query.getAt(FeedQuery.IDPERMALINK, row, null))
					},false,splitString
			);
			tag(xml,3,new Pair<String,Object>("pubDate",query.getAt(FeedQuery.PUBLISHEDDATE, row, null)),null,false,splitString);
			tag(xml,3,
					new Pair<String,Object>("source",query.getAt(FeedQuery.SOURCE, row, null)),
					new Pair[]{
						new Pair<String,Object>("url",query.getAt(FeedQuery.SOURCEURL, row, null))
					},false,false
			);
			
			
			
			
			
			append(xml,2,"</item>");
		}
		
		append(xml,1,"</channel>");
		append(xml,0,"</rss>");
		
	}
	
	private void propTag(Struct props,StringBuffer xml, int count,String[] srcNames,String trgName,String[][] attrNames) throws PageException {
		propTag(props, xml, count, srcNames, trgName, attrNames,false);
	}
		
	
	private void propTag(Struct props,StringBuffer xml, int count,String[] srcNames,String trgName,String[][] attrNames, boolean childrenAsTag) throws PageException {
		Object value;
		for(int i=0;i<srcNames.length;i++) {
			value=props.get(srcNames[i], null);
			
			if(value instanceof Array){
				Array arr = (Array)value;
				int size = arr.size();
				for(int y=1;y<=size;y++) {
					propTag(xml, count, arr.get(y, null), trgName, attrNames, childrenAsTag);
				}
				break;
			}
			if(value !=null)	{
				propTag(xml, count, value, trgName, attrNames, childrenAsTag);
				break;
			}
		}
	}
	
	private boolean propTag(StringBuffer xml, int count,Object value,String trgName,String[][] attrNames, boolean childrenAsTag) throws PageException {
		if(!StringUtil.isEmpty(value)){
			Pair[] attrs;
			if(value instanceof Struct && attrNames!=null) {
				Struct sct = (Struct)value;
				Object attrValue;
				ArrayList al=new ArrayList();
				for(int i=0;i<attrNames.length;i++) {
					attrValue=sct.get(attrNames[i][0], null);
					if(attrValue!=null) {
						al.add(new Pair<String,Object>(attrNames[i][1],attrValue));
					}
				}
				attrs=(Pair[]) al.toArray(new Pair[al.size()]);
			}
			else attrs=null;
			tag(xml,count,new Pair<String,Object>(trgName,FeedQuery.getValue(value)),attrs,false,false,childrenAsTag);
			return true;
		}
		return false;
		
	}
	private void tag(StringBuffer xml, int count, Pair<String,Object> tag, Pair<String,Object>[] attrs,boolean required,boolean splitString) throws PageException {
		tag(xml, count, tag, attrs, required, splitString,false);
	}
	
	private void tag(StringBuffer xml, int count, Pair<String,Object> tag, Pair<String,Object>[] attrs,boolean required,boolean splitString, boolean childrenAsTag) throws PageException {
		if(!required && StringUtil.isEmpty(tag.getValue())){
			if(attrs==null || attrs.length==0)return;
			int c=0;
			for(int i=0;i<attrs.length;i++){
				if(!StringUtil.isEmpty(attrs[i].getValue())) c++;
				
			}
			if(c==0)return;
		}
		
		if(tag.getValue() instanceof Array){
			Array arr=(Array) tag.getValue();
			int len=arr.size();
			for(int i=1;i<=len;i++) {
				_tag(xml, tag.getName(),arr.get(i, null), attrs, count, i,false,childrenAsTag);
			}
			return;
			
		}
		if(splitString && tag.getValue() instanceof String)	{
			String strValue=(String) tag.getValue();
			Array arr = ListUtil.listToArray(strValue, ',');
			if(arr.size()>1){
				int len=arr.size();
				for(int i=1;i<=len;i++) {
					_tag(xml, tag.getName(),arr.get(i, null), attrs, count, i,true,childrenAsTag);
				}
				return;
			}
		}
		_tag(xml, tag.getName(), tag.getValue(), attrs, count, 0,false,childrenAsTag);
		
		
	}
	
	private void _tag(StringBuffer xml, String tagName,Object tagValue, Pair<String,Object>[] attrs,int count, int index, boolean splitString, boolean childrenAsTag) throws PageException {
		for(int i=0;i<count;i++)xml.append("\t");
		xml.append('<');
		xml.append(tagName);
		
		Object attrValue;
		if(attrs!=null && !childrenAsTag){
			for(int i=0;i<attrs.length;i++){
				attrValue=attrs[i].getValue();
				if(index>0){
					if(attrValue instanceof Array)attrValue=((Array)attrValue).get(index,null);
					else if(splitString && attrValue instanceof String){
						Array arr = ListUtil.listToArray((String)attrValue, ',');
						attrValue=arr.get(index,null);
					}
				}
				if(StringUtil.isEmpty(attrValue)) continue;
				
				xml.append(' ');
				xml.append(attrs[i].getName());
				xml.append("=\"");
				xml.append(XMLUtil.escapeXMLString(toString(attrValue)));
				xml.append("\"");
			}
		}
		
		xml.append('>');
			xml.append(toString(tagValue));
			if(attrs!=null && attrs.length>0 && childrenAsTag){
				xml.append('\n');
				for(int i=0;i<attrs.length;i++){
					attrValue=attrs[i].getValue();
					if(index>0){
						if(attrValue instanceof Array)attrValue=((Array)attrValue).get(index,null);
						else if(splitString && attrValue instanceof String){
							Array arr = ListUtil.listToArray((String)attrValue, ',');
							attrValue=arr.get(index,null);
						}
					}
					if(StringUtil.isEmpty(attrValue)) continue;

					for(int y=0;y<count+1;y++)xml.append("\t");
					xml.append('<');
					xml.append(attrs[i].getName());
					xml.append('>');
					//xml.append(XMLUtil.escapeXMLString(toString(attrValue)));
					xml.append(toString(attrValue));
					xml.append("</");
					xml.append(attrs[i].getName());
					xml.append(">\n");
				}
				for(int y=0;y<count;y++)xml.append("\t");
			}
			
			
			
			
		xml.append("</");
		xml.append(tagName);
		xml.append(">\n");	

	}
	
	
	private String toString(Object value) throws PageException {
		if(Decision.isDateAdvanced(value, false)) return GetHttpTimeString.invoke(Caster.toDatetime(value, pageContext.getTimeZone()));
		 return XMLUtil.escapeXMLString(Caster.toString(value));
	}
	
	private static void append(StringBuffer xml, int count, String value) {
		for(int i=0;i<count;i++)xml.append("\t");
		xml.append(value);
		xml.append("\n");
	}
	
	private void doActionRead() throws IOException, SAXException, PageException {
		required("Feed", "read", "source", source);

		if(outputFile!=null && outputFile.exists() && !overwrite)
			throw new ApplicationException("outputFile file ["+outputFile+"] already exists");
		
		
		String charset=null;
		
// plain output
		// xmlVar
		if(outputFile!=null) {
			IOUtil.copy(source, outputFile);
		}
		// outputFile
		String strFeed=null;
		if(!StringUtil.isEmpty(xmlVar)) {
			strFeed = IOUtil.toString(outputFile!=null?outputFile:source,charset);
			pageContext.setVariable(xmlVar, strFeed);
		}
		// Input Source
		InputSource is=null;
		Reader r=null;
		if(strFeed!=null)is=new InputSource(new StringReader(strFeed));
		else if(outputFile!=null)is=new InputSource(r=IOUtil.getReader(outputFile, charset));
		else is=new InputSource(r=IOUtil.getReader(source, charset));
		is.setSystemId(source.getPath());
		
		try{
			FeedHandler feed=new FeedHandler(source);
			Struct data = feed.getData();
			//print.e(data.keys());
			//print.e(data);
			// properties
			if(properties!=null) {
				String strProp = Caster.toString(properties,null);
				if(strProp==null)throw new ApplicationException("attribute [properties] should be of type string");
				pageContext.setVariable(strProp, FeedProperties.toProperties(data));
			}
			
			// query or enclosure
			railo.runtime.type.Query qry=null;
			if(query!=null || enclosureDir!=null){
				qry=FeedQuery.toQuery(data,feed.hasDC());
			}
			
			// query
			if(query!=null) {
				String strQuery = Caster.toString(query,null);
				if(strQuery==null)throw new ApplicationException("attribute [query] should be of type string");
				pageContext.setVariable(strQuery, qry);
			}
			// enclosure
			
			if(enclosureDir!=null) {
				int rows=qry.getRowCount();
				String strUrl=null;
				Resource src,dest;
				for(int row=1;row<=rows;row++) {
					strUrl=Caster.toString(qry.getAt(FeedQuery.LINKHREF, row, null),null);
					if(!StringUtil.isEmpty(strUrl)){
						src=ResourceUtil.toResourceNotExisting(pageContext, strUrl);
						dest=enclosureDir.getRealResource(src.getName());
						if(!ignoreEnclosureError && !overwriteEnclosure && dest.exists())
							throw new ApplicationException("enclosure file ["+dest+"] already exists");
						try {
						IOUtil.copy(src, dest);
						}
						catch(IOException ioe) {
							if(!ignoreEnclosureError)throw ioe;
						}
					}
				}
			}
			
			// name
			if(name!=null) {
				String strName = Caster.toString(name,null);
				if(strName==null)throw new ApplicationException("attribute [name] should be of type string");
				pageContext.setVariable(strName, data);
			}
			
			
			
		}
		finally {
			IOUtil.closeEL(r);
		}
	}
}
