001/** 002 * 003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved. 004 * 005 * This library is free software; you can redistribute it and/or 006 * modify it under the terms of the GNU Lesser General Public 007 * License as published by the Free Software Foundation; either 008 * version 2.1 of the License, or (at your option) any later version. 009 * 010 * This library is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013 * Lesser General Public License for more details. 014 * 015 * You should have received a copy of the GNU Lesser General Public 016 * License along with this library. If not, see <http://www.gnu.org/licenses/>. 017 * 018 **/ 019package lucee.runtime.text.feed; 020 021import java.io.IOException; 022import java.io.InputStream; 023import java.util.HashMap; 024import java.util.Iterator; 025import java.util.Map; 026import java.util.Map.Entry; 027import java.util.Stack; 028 029import lucee.commons.io.IOUtil; 030import lucee.commons.io.SystemUtil; 031import lucee.commons.io.res.Resource; 032import lucee.commons.lang.StringUtil; 033import lucee.runtime.op.Caster; 034import lucee.runtime.text.xml.XMLUtil; 035import lucee.runtime.type.Array; 036import lucee.runtime.type.ArrayImpl; 037import lucee.runtime.type.CastableArray; 038import lucee.runtime.type.Collection; 039import lucee.runtime.type.Collection.Key; 040import lucee.runtime.type.KeyImpl; 041import lucee.runtime.type.Struct; 042import lucee.runtime.type.StructImpl; 043import lucee.runtime.type.util.KeyConstants; 044 045import org.xml.sax.Attributes; 046import org.xml.sax.InputSource; 047import org.xml.sax.Locator; 048import org.xml.sax.SAXException; 049import org.xml.sax.XMLReader; 050import org.xml.sax.ext.Locator2; 051import org.xml.sax.helpers.DefaultHandler; 052 053public final class FeedHandler extends DefaultHandler { 054 055 public final static String DEFAULT_SAX_PARSER="org.apache.xerces.parsers.SAXParser"; 056 057 058 private XMLReader xmlReader; 059 060 //private StringBuffer content=new StringBuffer(); 061 062 private int deep=0; 063 private FeedStruct data; 064 private String path=""; 065 private Collection.Key inside; 066 private Stack<FeedStruct> parents=new Stack<FeedStruct>(); 067 private FeedDeclaration decl; 068 private Map<String,String> root=new HashMap<String,String>(); 069 private boolean hasDC; 070 private boolean isAtom; 071 072 private boolean inAuthor; 073 074 private boolean inEntry; 075 076 077 /** 078 * Constructor of the class 079 * @param res 080 * @throws IOException 081 * @throws SAXException 082 */ 083 public FeedHandler(Resource res) throws IOException, SAXException { 084 InputStream is=null; 085 try { 086 InputSource source=new InputSource(is=res.getInputStream()); 087 source.setSystemId(res.getPath()); 088 089 init(DEFAULT_SAX_PARSER,source); 090 } 091 finally { 092 IOUtil.closeEL(is); 093 } 094 } 095 public FeedHandler(InputSource is) throws IOException, SAXException { 096 init(DEFAULT_SAX_PARSER,is); 097 098 } 099 100 /** 101 * Constructor of the class 102 * @param stream 103 * @throws IOException 104 * @throws SAXException 105 */ 106 public FeedHandler(InputStream stream) throws IOException, SAXException { 107 InputSource is=new InputSource(IOUtil.getReader(stream, SystemUtil.getCharset())); 108 init(DEFAULT_SAX_PARSER,is); 109 } 110 111 private void init(String saxParser,InputSource is) throws SAXException, IOException { 112 //print.out("is:"+is); 113 hasDC=false; 114 data=new FeedStruct(); 115 xmlReader=XMLUtil.createXMLReader(saxParser); 116 xmlReader.setContentHandler(this); 117 xmlReader.setErrorHandler(this); 118 xmlReader.setDTDHandler(new DummyDTDHandler()); 119 xmlReader.parse(is); 120 } 121 122 /** 123 * @return the hasDC 124 */ 125 public boolean hasDC() { 126 return hasDC; 127 } 128 public void setDocumentLocator(Locator locator) { 129 if (locator instanceof Locator2) { 130 Locator2 locator2 = (Locator2) locator; 131 root.put("encoding", locator2.getEncoding()); 132 } 133 } 134 135 @Override 136 public void startElement(String uri, String name, String qName, Attributes atts) { 137 deep++; 138 139 140 if("entry".equals(name))inEntry=true; 141 else if("author".equals(name))inAuthor=true; 142 143 if(qName.startsWith("dc:")){ 144 name="dc_"+name; 145 hasDC=true; 146 } 147 //print.o("iniside("+deep+"):"+name+"->"+uri); 148 149 150 inside = KeyImpl.getInstance(name); 151 if(StringUtil.isEmpty(path))path=name; 152 else { 153 path+="."+name; 154 } 155 if(decl==null){ 156 String decName=name; 157 String version = atts.getValue("version"); 158 if("feed".equals(decName)) { 159 if(!StringUtil.isEmpty(version))decName="atom_"+version; 160 else decName="atom_1.0"; 161 } 162 else { 163 if(!StringUtil.isEmpty(version))decName+="_"+version; 164 } 165 decl=FeedDeclaration.getInstance(decName); 166 root.put("version",decName); 167 isAtom=decl.getType().equals("atom"); 168 169 } 170 171 172 FeedStruct sct=new FeedStruct(path,inside,uri); 173 174 // attributes 175 Map<String,String> attrs = getAttributes(atts, path); 176 if(attrs!=null){ 177 Entry<String, String> entry; 178 Iterator<Entry<String, String>> it = attrs.entrySet().iterator(); 179 sct.setHasAttribute(true); 180 while(it.hasNext()){ 181 entry = it.next(); 182 sct.setEL(entry.getKey(), entry.getValue()); 183 } 184 } 185 186 // assign 187 if(!isAtom || deep<4) { 188 Object obj = data.get(inside, null); 189 if(obj instanceof Array) { 190 ((Array)obj).appendEL(sct); 191 } 192 else if(obj instanceof FeedStruct){ 193 Array arr = new ArrayImpl(); 194 arr.appendEL(obj); 195 arr.appendEL(sct); 196 data.setEL(inside, arr); 197 } 198 else if(obj instanceof String){ 199 // wenn wert schon existiert wird castableArray in setContent erstellt 200 } 201 else { 202 El el= decl.getDeclaration().get(path); 203 if(el!=null && (el.getQuantity()==El.QUANTITY_0_N || el.getQuantity()==El.QUANTITY_1_N)){ 204 Array arr = new ArrayImpl(); 205 arr.appendEL(sct); 206 data.setEL(inside, arr); 207 } 208 else data.setEL(inside, sct); 209 210 } 211 } 212 parents.add(data); 213 data=sct; 214 215 //<enclosure url="http://www.scripting.com/mp3s/weatherReportDicksPicsVol7.mp3" length="6182912" type="audio/mpeg"/> 216 } 217 218 219 public void endElement(String uri, String name, String qName) { 220 if("entry".equals(name))inEntry=false; 221 else if("author".equals(name))inAuthor=false; 222 deep--; 223 if(isAtom && deep>=(inEntry && inAuthor?4:3)) { 224 String content = data.getString(); 225 Key[] keys = data.keys(); 226 StringBuilder sb=new StringBuilder(); 227 sb.append("<"); 228 sb.append(qName); 229 230 // xmlns 231 if(!parents.peek().getUri().equals(uri)) { 232 sb.append(" xmlns=\""); 233 sb.append(uri); 234 sb.append("\""); 235 } 236 237 for(int i=0;i<keys.length;i++){ 238 sb.append(" "); 239 sb.append(keys[i].getString()); 240 sb.append("=\""); 241 sb.append(Caster.toString(data.get(keys[i],""),"")); 242 sb.append("\""); 243 244 } 245 246 247 if(!StringUtil.isEmpty(content)) { 248 sb.append(">"); 249 sb.append(content); 250 sb.append("</"+qName+">"); 251 } 252 else sb.append("/>"); 253 254 255 256 257 258 259 data= parents.pop(); 260 data.append(sb.toString().trim()); 261 //setContent(sb.toString().trim()); 262 263 path=data.getPath(); 264 inside=data.getInside(); 265 return; 266 } 267 268 269 270 //setContent(content.toString().trim()); 271 setContent(data.getString().trim()); 272 273 274 275 data=parents.pop(); 276 path=data.getPath(); 277 inside=data.getInside(); 278 } 279 280 public void characters (char ch[], int start, int length) { 281 data.append(new String(ch,start,length)); 282 //content.append(new String(ch,start,length)); 283 } 284 285 private void setContent(String value) { 286 //print.out(path+":"+inside); 287 if(StringUtil.isEmpty(inside)) return; 288 289 if(data.hasAttribute()) { 290 if(!StringUtil.isEmpty(value))setEl(data,KeyConstants._value,value); 291 } 292 else { 293 FeedStruct parent=parents.peek(); 294 setEl(parent,inside,value); 295 } 296 297 } 298 299 private void setEl(Struct sct, Collection.Key key, String value) { 300 Object existing = sct.get(key,null); 301 302 if(existing instanceof CastableArray){ 303 ((CastableArray)existing).appendEL(value); 304 } 305 else if(existing instanceof String){ 306 CastableArray ca=new CastableArray(existing); 307 ca.appendEL(existing); 308 ca.appendEL(value); 309 sct.setEL(key,ca); 310 } 311 else 312 sct.setEL(key,Caster.toString(value)); 313 314 315 /*if(existing instanceof Struct)sct.setEL(key,value); 316 else if(existing instanceof Array)((Array)existing).appendEL(value); 317 else if(existing!=null){ 318 CastableArray ca=new CastableArray(existing); 319 ca.appendEL(existing); 320 ca.appendEL(value); 321 sct.setEL(key,ca); 322 } 323 else*/ 324 } 325 326 private Map<String,String> getAttributes(Attributes attrs, String path) { 327 El el =decl.getDeclaration().get(path); 328 329 int len=attrs.getLength(); 330 if((el==null || el.getAttrs()==null) && len==0) return null; 331 332 Map<String,String> map=new HashMap<String,String>(); 333 if(el!=null) { 334 Attr[] defaults = el.getAttrs(); 335 if(defaults!=null) { 336 for(int i=0;i<defaults.length;i++) { 337 if(defaults[i].hasDefaultValue()) 338 map.put(defaults[i].getName(), defaults[i].getDefaultValue()); 339 } 340 } 341 } 342 for(int i=0;i<len;i++) { 343 map.put(attrs.getQName(i), attrs.getValue(i)); 344 } 345 return map; 346 } 347 348 349 /** 350 * @return the properties 351 */ 352 public Struct getData() { 353 return data; 354 } 355 356 357 358 359 @Override 360 public void endDocument() throws SAXException { 361 super.endDocument(); 362 Struct def=new StructImpl(); 363 Key[] entryLevel = decl.getEntryLevel(); 364 for(int i=0;i<entryLevel.length;i++) { 365 data=(FeedStruct) data.get(entryLevel[i],def); 366 } 367 data.putAll(root); 368 } 369 370 371}