rebuilding head
[mir.git] / source / mir / rss / RSSReader.java
1 /*
2  * Copyright (C) 2001, 2002 The Mir-coders group
3  *
4  * This file is part of Mir.
5  *
6  * Mir is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * Mir is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Mir; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  *
20  * In addition, as a special exception, The Mir-coders gives permission to link
21  * the code of this program with  any library licensed under the Apache Software License,
22  * The Sun (tm) Java Advanced Imaging library (JAI), The Sun JIMI library
23  * (or with modified versions of the above that use the same license as the above),
24  * and distribute linked combinations including the two.  You must obey the
25  * GNU General Public License in all respects for all of the code used other than
26  * the above mentioned libraries.  If you modify this file, you may extend this
27  * exception to your version of the file, but you are not obligated to do so.
28  * If you do not wish to do so, delete this exception statement from your version.
29  */
30 package mir.rss;
31
32 import java.io.InputStream;
33 import java.util.ArrayList;
34 import java.util.HashMap;
35 import java.util.List;
36 import java.util.Map;
37
38 import mir.util.DateTimeFunctions;
39 import mir.util.HTTPClientHelper;
40 import mir.util.xml.XMLParserEngine;
41 import mir.util.xml.XMLParserExc;
42 import mir.util.xml.XMLParserFailure;
43
44 /**
45  *
46  * <p>Title: </p>
47  * <p>Description: </p>
48  * <p>Copyright: Copyright (c) 2003</p>
49  * <p>Company: </p>
50  * @author not attributable
51  * @version 1.0
52  */
53
54 public class RSSReader {
55   public static final String RDF_NAMESPACE_URI = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
56   public static final String RSS_1_0_NAMESPACE_URI = "http://purl.org/rss/1.0/";
57   public static final String RSS_0_9_NAMESPACE_URI = "http://my.netscape.com/rdf/simple/0.9/";
58   public static final String DUBLINCORE_NAMESPACE_URI = "http://purl.org/dc/elements/1.1/";
59   public static final String EVENT_NAMESPACE_URI = "http://purl.org/rss/1.0/modules/event/";
60   public static final String TAXONOMY_NAMESPACE_URI = "http://web.resource.org/rss/1.0/modules/taxonomy/";
61   public static final String DUBLINCORE_TERMS_NAMESPACE_URI = "http://purl.org/dc/terms/";
62   public static final String CONTENT_NAMESPACE_URI = "http://purl.org/rss/1.0/modules/content/";
63
64   // ML: to be localized:
65   public static final String V2V_NAMESPACE_URI = "http://v2v.cc/rss/";
66
67   private static final mir.util.xml.XMLName RDF_ABOUT_PARAMETER = new mir.util.xml.XMLName(RDF_NAMESPACE_URI, "about");
68   private static final mir.util.xml.XMLName RDF_SEQUENCE_TAG = new mir.util.xml.XMLName(RDF_NAMESPACE_URI, "Seq");
69   private static final mir.util.xml.XMLName RDF_BAG_PARAMETER = new mir.util.xml.XMLName(RDF_NAMESPACE_URI, "Bag");
70
71   private static final mir.util.xml.XMLName RSS_CHANNEL_TAG = new mir.util.xml.XMLName(RSS_1_0_NAMESPACE_URI, "channel");
72   private static final mir.util.xml.XMLName RSS_ITEM_TAG = new mir.util.xml.XMLName(RSS_1_0_NAMESPACE_URI, "item");
73   private static final mir.util.xml.XMLName RSS_ITEMS_TAG = new mir.util.xml.XMLName(RSS_1_0_NAMESPACE_URI, "items");
74
75   private List modules;
76   private Map namespaceURItoModule;
77   private Map moduleToPrefix;
78
79   public RSSReader() {
80     modules = new ArrayList();
81     namespaceURItoModule = new HashMap();
82     moduleToPrefix = new HashMap();
83
84     registerModule(new RSSBasicModule(RDF_NAMESPACE_URI, "RDF module"), "rdf");
85     registerModule(new RSSBasicModule(RSS_1_0_NAMESPACE_URI, "RSS 1.0 module"), "rss");
86     registerModule(new RSSBasicModule(RSS_0_9_NAMESPACE_URI, "RSS 0.9 module"), "rss");
87
88     RSSBasicModule dcModule = new RSSBasicModule(DUBLINCORE_NAMESPACE_URI, "RSS Dublin Core 1.1");
89     dcModule.addProperty("date", RSSModule.W3CDTF_PROPERTY_TYPE);
90     registerModule(dcModule, "dc");
91
92     RSSBasicModule dcTermsModule = new RSSBasicModule(DUBLINCORE_TERMS_NAMESPACE_URI, "RSS Qualified Dublin core");
93     dcTermsModule.addProperty("created", RSSModule.W3CDTF_PROPERTY_TYPE);
94     dcTermsModule.addProperty("issued", RSSModule.W3CDTF_PROPERTY_TYPE);
95     dcTermsModule.addProperty("modified", RSSModule.W3CDTF_PROPERTY_TYPE);
96     dcTermsModule.addProperty("dateAccepted", RSSModule.W3CDTF_PROPERTY_TYPE);
97     dcTermsModule.addProperty("dateCopyrighted", RSSModule.W3CDTF_PROPERTY_TYPE);
98     dcTermsModule.addProperty("dateSubmitted", RSSModule.W3CDTF_PROPERTY_TYPE);
99     registerModule(dcTermsModule, "dcterms");
100
101     RSSBasicModule v2vTermsModule = new RSSBasicModule(V2V_NAMESPACE_URI, "indymedia v2v RSS module");
102     v2vTermsModule.addMultiValuedProperty("topic", RSSModule.PCDATA_PROPERTY_TYPE);
103     v2vTermsModule.addMultiValuedProperty("genre", RSSModule.PCDATA_PROPERTY_TYPE);
104     v2vTermsModule.addMultiValuedProperty("link", RSSModule.PCDATA_PROPERTY_TYPE);
105     registerModule(v2vTermsModule, "v2v");
106
107     registerModule(new RSSBasicModule(EVENT_NAMESPACE_URI, "Event RSS module"), "ev");
108     registerModule(new RSSBasicModule(TAXONOMY_NAMESPACE_URI, "Taxonomy RSS module"), "taxo");
109     registerModule(new RSSBasicModule(CONTENT_NAMESPACE_URI  , "Content RSS module"), "content");
110   }
111
112   public void registerModule(RSSModule aModule, String aPrefix) {
113     modules.add(aModule);
114     namespaceURItoModule.put(aModule.getNamespaceURI(), aModule);
115     moduleToPrefix.put(aModule, aPrefix);
116   }
117
118   public RSSData parseInputStream(InputStream aStream) throws RSSExc, RSSFailure {
119     try {
120       RSSData result = new RSSData();
121       XMLParserEngine.getInstance().parse("xml", aStream, new RootSectionHandler(result));
122
123       return result;
124     }
125     catch (Throwable t) {
126       throw new RSSFailure(t);
127     }
128   }
129
130   public RSSData parseInputStream(InputStream aStream, String anEncoding) throws RSSExc, RSSFailure {
131     try {
132       RSSData result = new RSSData();
133       XMLParserEngine.getInstance().parse("xml", aStream, anEncoding, new RootSectionHandler(result));
134
135       return result;
136     }
137     catch (Throwable t) {
138       throw new RSSFailure(t);
139     }
140   }
141
142   public RSSData parseUrl(String anUrl) throws RSSExc, RSSFailure {
143     try {
144       HTTPClientHelper httpClientHelper = new HTTPClientHelper();       
145       InputStream inputStream = httpClientHelper.getUrl(anUrl);
146       if (inputStream==null)
147         throw new RSSExc("RSSChannel.parseUrl: Can't get url content");
148
149       RSSData theRSSData =  parseInputStream(inputStream);
150       httpClientHelper.releaseHTTPConnection();
151       return theRSSData;
152     }
153     catch (Throwable t) {
154       throw new RSSFailure(t);
155     }
156   }
157
158   public RSSData parseUrl(String anUrl, String anEncoding) throws RSSExc, RSSFailure {
159     try {
160       HTTPClientHelper httpClientHelper = new HTTPClientHelper();       
161       InputStream inputStream = httpClientHelper.getUrl(anUrl);
162       if (inputStream==null)
163         throw new RSSExc("RSSChannel.parseUrl: Can't get url content");
164
165       RSSData theRSSData =  parseInputStream(inputStream, anEncoding);
166       httpClientHelper.releaseHTTPConnection();
167       return theRSSData;
168     }
169     catch (Throwable t) {
170       throw new RSSFailure(t);
171     }
172   }
173
174   private class RootSectionHandler extends mir.util.xml.AbstractSectionHandler {
175     private RSSData data;
176
177     public RootSectionHandler(RSSData aData) {
178       data = aData;
179     }
180
181     public mir.util.xml.SectionHandler startElement(mir.util.xml.XMLName aTag, Map anAttributes) throws XMLParserExc {
182       if (aTag.getLocalName().equals("RDF")) {
183         return new RDFSectionHandler(data);
184       }
185       else
186         throw new XMLParserFailure(new RSSExc("'RDF' tag expected"));
187     };
188
189     public void endElement(mir.util.xml.SectionHandler aHandler) throws XMLParserExc {
190     };
191
192     public void characters(String aCharacters) throws XMLParserExc {
193       if (aCharacters.trim().length()>0)
194         throw new XMLParserExc("No character data allowed here");
195     };
196
197     public void finishSection() throws XMLParserExc {
198     };
199   }
200
201   private class RDFSectionHandler extends mir.util.xml.AbstractSectionHandler {
202     private RSSData data;
203
204
205     public RDFSectionHandler(RSSData aData) {
206       data = aData;
207     }
208
209     public mir.util.xml.SectionHandler startElement(mir.util.xml.XMLName aTag, Map anAttributes) throws XMLParserExc {
210       String identifier = (String) anAttributes.get(RDF_ABOUT_PARAMETER);
211       String rdfClass = makeQualifiedName(aTag);
212
213       return new RDFResourceSectionHandler(rdfClass, identifier);
214     };
215
216     public void endElement(mir.util.xml.SectionHandler aHandler) throws XMLParserExc {
217       if (aHandler instanceof RDFResourceSectionHandler) {
218         data.addResource(((RDFResourceSectionHandler) aHandler).getResource());
219       }
220     };
221
222     public void characters(String aCharacters) throws XMLParserExc {
223       if (aCharacters.trim().length()>0)
224         throw new XMLParserExc("No character data allowed here");
225     };
226
227     public void finishSection() throws XMLParserExc {
228     };
229   }
230
231   private mir.util.xml.SectionHandler makePropertyValueSectionHandler(mir.util.xml.XMLName aTag, Map anAttributes) {
232     RSSModule module = (RSSModule) namespaceURItoModule.get(aTag.getNamespaceURI());
233
234     if (module!=null) {
235       RSSModule.RSSModuleProperty property = module.getPropertyForName(aTag.getLocalName());
236
237       if (property!=null) {
238         switch (property.getType()) {
239           case
240             RSSModule.PCDATA_PROPERTY_TYPE:
241               return new PCDATASectionHandler();
242           case
243             RSSModule.RDFCOLLECTION_PROPERTY_TYPE:
244               return new RDFCollectionSectionHandler();
245 //          case
246 //            RSSModule.RDF_PROPERTY_TYPE:
247 //              return new RDFValueSectionHandler();
248           case
249             RSSModule.W3CDTF_PROPERTY_TYPE:
250               return new DateSectionHandler();
251         }
252       }
253     }
254
255     return new FlexiblePropertyValueSectionHandler();
256   }
257
258   private void usePropertyValueSectionHandler(RDFResource aResource, PropertyValueSectionHandler aHandler, mir.util.xml.XMLName aTag) {
259     RSSModule module = (RSSModule) namespaceURItoModule.get(aTag.getNamespaceURI());
260
261     if (module!=null) {
262       RSSModule.RSSModuleProperty property = module.getPropertyForName(aTag.getLocalName());
263
264       if (property!=null && property.getIsMultiValued()) {
265         List value = (List) aResource.get(makeQualifiedName(aTag));
266
267         if (value==null) {
268           value = new ArrayList();
269           aResource.set(makeQualifiedName(aTag), value);
270         }
271
272         value.add(aHandler.getValue());
273
274         return;
275       }
276     }
277
278     aResource.set(makeQualifiedName(aTag), aHandler.getValue());
279   }
280
281   private String makeQualifiedName(mir.util.xml.XMLName aName) {
282     String result=aName.getLocalName();
283     RSSModule module = (RSSModule) namespaceURItoModule.get(aName.getNamespaceURI());
284     if (module!=null) {
285       String prefix = (String) moduleToPrefix.get(module);
286
287       if (prefix!=null && prefix.length()>0)
288         result = prefix+":"+result;
289     }
290
291     return result;
292   }
293
294   private class RDFResourceSectionHandler extends mir.util.xml.AbstractSectionHandler {
295     private String image;
296     private mir.util.xml.XMLName currentTag;
297     private RDFResource resource;
298
299     public RDFResourceSectionHandler(String anRDFClass, String anIdentifier) {
300       resource = new RDFResource(anRDFClass, anIdentifier);
301     }
302
303     public mir.util.xml.SectionHandler startElement(mir.util.xml.XMLName aTag, Map anAttributes) throws XMLParserExc {
304       currentTag = aTag;
305
306       return makePropertyValueSectionHandler(aTag, anAttributes);
307     };
308
309     public void endElement(mir.util.xml.SectionHandler aHandler) throws XMLParserExc {
310       if (aHandler instanceof PropertyValueSectionHandler) {
311         usePropertyValueSectionHandler(resource, (PropertyValueSectionHandler) aHandler, currentTag);
312 //        resource.set(makeQualifiedName(currentTag), ( (PropertyValueSectionHandler) aHandler).getFieldValue());
313       }
314     };
315
316     public void characters(String aCharacters) throws XMLParserExc {
317       if (aCharacters.trim().length()>0)
318         throw new XMLParserExc("No character data allowed here");
319     };
320
321     public void finishSection() throws XMLParserExc {
322     };
323
324     public RDFResource getResource() {
325       if ((resource.getIdentifier()==null || resource.getIdentifier().length()==0) && resource.get("rss:link")!=null) {
326         resource.setIdentifier(resource.get("rss:link").toString());
327       }
328
329       return resource;
330     }
331   }
332
333   private abstract class PropertyValueSectionHandler extends mir.util.xml.AbstractSectionHandler {
334     public abstract Object getValue();
335   }
336
337   private class FlexiblePropertyValueSectionHandler extends PropertyValueSectionHandler {
338     private StringBuffer stringData;
339     private Object structuredData;
340
341     public FlexiblePropertyValueSectionHandler() {
342       stringData = new StringBuffer();
343       structuredData=null;
344     }
345
346     public mir.util.xml.SectionHandler startElement(String aTag, Map anAttributes) throws XMLParserExc {
347       if (aTag.equals(RDF_SEQUENCE_TAG))
348         return new RDFSequenceSectionHandler();
349       else
350         return new DiscardingSectionHandler();
351     };
352
353     public void endElement(mir.util.xml.SectionHandler aHandler) throws XMLParserExc {
354       if (aHandler instanceof RDFSequenceSectionHandler) {
355         structuredData= ((RDFSequenceSectionHandler) aHandler).getItems();
356       }
357     };
358
359     public void characters(String aCharacters) throws XMLParserExc {
360       stringData.append(aCharacters);
361     };
362
363     public void finishSection() throws XMLParserExc {
364     };
365
366     public String getData() {
367       return stringData.toString();
368     }
369
370     public Object getValue() {
371       if (structuredData==null)
372         return stringData.toString();
373       else
374         return structuredData;
375     }
376   }
377
378   private class RDFCollectionSectionHandler extends PropertyValueSectionHandler {
379     private List items;
380
381     public RDFCollectionSectionHandler() {
382       items = new ArrayList();
383     }
384
385     public mir.util.xml.SectionHandler startElement(String aTag, Map anAttributes) throws XMLParserExc {
386       if (aTag.equals(RDF_SEQUENCE_TAG))
387         return new RDFSequenceSectionHandler();
388       else
389         return new DiscardingSectionHandler();
390     };
391
392     public void endElement(mir.util.xml.SectionHandler aHandler) throws XMLParserExc {
393       if (aHandler instanceof RDFSequenceSectionHandler) {
394         items.addAll(((RDFSequenceSectionHandler) aHandler).getItems());
395       }
396     };
397
398     public void characters(String aCharacters) throws XMLParserExc {
399       if (aCharacters.trim().length()>0)
400         throw new XMLParserExc("No character data allowed here");
401     };
402
403     public void finishSection() throws XMLParserExc {
404     };
405
406     public List getItems() {
407       return items;
408     }
409
410     public Object getValue() {
411       return items;
412     }
413   }
414
415   private class PCDATASectionHandler extends PropertyValueSectionHandler {
416     private StringBuffer data;
417
418     public PCDATASectionHandler() {
419       data = new StringBuffer();
420     }
421
422     public mir.util.xml.SectionHandler startElement(String aTag, Map anAttributes) throws XMLParserExc {
423       throw new XMLParserFailure(new RSSExc("No subtags allowed here"));
424     };
425
426     public void endElement(mir.util.xml.SectionHandler aHandler) throws XMLParserExc {
427     };
428
429     public void characters(String aCharacters) throws XMLParserExc {
430       data.append(aCharacters);
431     };
432
433     public void finishSection() throws XMLParserExc {
434     };
435
436     public String getData() {
437       return data.toString();
438     }
439
440     public Object getValue() {
441       return data.toString();
442     }
443   }
444
445   private class DateSectionHandler extends PropertyValueSectionHandler {
446     private StringBuffer data;
447
448     public DateSectionHandler() {
449       data = new StringBuffer();
450     }
451
452     public mir.util.xml.SectionHandler startElement(String aTag, Map anAttributes) throws XMLParserExc {
453       throw new XMLParserFailure(new RSSExc("No subtags allowed here"));
454     };
455
456     public void endElement(mir.util.xml.SectionHandler aHandler) throws XMLParserExc {
457     };
458
459     public void characters(String aCharacters) throws XMLParserExc {
460       data.append(aCharacters);
461     };
462
463     public void finishSection() throws XMLParserExc {
464     };
465
466     public Object getValue() {
467       try {
468         String expression = data.toString().trim();
469
470         return DateTimeFunctions.parseW3CDTFString(expression);
471       }
472       catch (Throwable t) {
473
474         return null;
475       }
476     }
477   }
478
479
480   private class RDFSequenceSectionHandler extends mir.util.xml.AbstractSectionHandler {
481     private List items;
482
483     public RDFSequenceSectionHandler() {
484       items = new ArrayList();
485     }
486
487     public mir.util.xml.SectionHandler startElement(String aTag, Map anAttributes) throws XMLParserExc {
488       if (aTag.equals("rdf:li")) {
489         String item = (String) anAttributes.get("rdf:resource");
490
491         if (item!=null)
492           items.add(item);
493       }
494
495       return new DiscardingSectionHandler();
496     };
497
498     public void endElement(mir.util.xml.SectionHandler aHandler) throws XMLParserExc {
499     };
500
501     public void characters(String aCharacters) throws XMLParserExc {
502     };
503
504     public void finishSection() throws XMLParserExc {
505     };
506
507     public List getItems() {
508       return items;
509     }
510   }
511
512   private class RDFLiteralSectionHandler extends PropertyValueSectionHandler {
513     private StringBuffer data;
514     private String tag;
515
516     public RDFLiteralSectionHandler() {
517       data = new StringBuffer();
518     }
519
520     protected StringBuffer getData() {
521       return data;
522     }
523
524     public mir.util.xml.SectionHandler startElement(String aTag, Map anAttributes) throws XMLParserExc {
525       tag=aTag;
526       data.append("<"+tag+">");
527
528       return new RDFLiteralSectionHandler();
529     };
530
531     public void endElement(mir.util.xml.SectionHandler aHandler) throws XMLParserExc {
532       data.append(((RDFLiteralSectionHandler) aHandler).getData());
533       data.append("</"+tag+">");
534     };
535
536     public void characters(String aCharacters) throws XMLParserExc {
537       data.append(aCharacters);
538     };
539
540     public void finishSection() throws XMLParserExc {
541     };
542
543     public Object getValue() {
544       return data.toString();
545     }
546   }
547
548   private class DiscardingSectionHandler extends mir.util.xml.AbstractSectionHandler {
549     public mir.util.xml.SectionHandler startElement(String aTag, Map anAttributes) throws XMLParserExc {
550       return this;
551     };
552
553     public void endElement(mir.util.xml.SectionHandler aHandler) throws XMLParserExc {
554     };
555
556     public void characters(String aCharacters) throws XMLParserExc {
557     };
558
559     public void finishSection() throws XMLParserExc {
560     };
561   }
562 }