ad2958a85a6c70ca3bced9094f6f7cc1e03410c6
[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 public class RSSReader {
45   public static final String RDF_NAMESPACE_URI = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
46   public static final String RSS_1_0_NAMESPACE_URI = "http://purl.org/rss/1.0/";
47   public static final String RSS_0_9_NAMESPACE_URI = "http://my.netscape.com/rdf/simple/0.9/";
48   public static final String DUBLINCORE_NAMESPACE_URI = "http://purl.org/dc/elements/1.1/";
49   public static final String EVENT_NAMESPACE_URI = "http://purl.org/rss/1.0/modules/event/";
50   public static final String TAXONOMY_NAMESPACE_URI = "http://web.resource.org/rss/1.0/modules/taxonomy/";
51   public static final String DUBLINCORE_TERMS_NAMESPACE_URI = "http://purl.org/dc/terms/";
52   public static final String CONTENT_NAMESPACE_URI = "http://purl.org/rss/1.0/modules/content/";
53
54   // ML: to be localized:
55   public static final String V2V_NAMESPACE_URI = "http://v2v.cc/rss/";
56
57   private static final mir.util.xml.XMLName RDF_ABOUT_PARAMETER = new mir.util.xml.XMLName(RDF_NAMESPACE_URI, "about");
58   private static final mir.util.xml.XMLName RDF_SEQUENCE_TAG = new mir.util.xml.XMLName(RDF_NAMESPACE_URI, "Seq");
59   private static final mir.util.xml.XMLName RDF_BAG_PARAMETER = new mir.util.xml.XMLName(RDF_NAMESPACE_URI, "Bag");
60
61   private static final mir.util.xml.XMLName RSS_CHANNEL_TAG = new mir.util.xml.XMLName(RSS_1_0_NAMESPACE_URI, "channel");
62   private static final mir.util.xml.XMLName RSS_ITEM_TAG = new mir.util.xml.XMLName(RSS_1_0_NAMESPACE_URI, "item");
63   private static final mir.util.xml.XMLName RSS_ITEMS_TAG = new mir.util.xml.XMLName(RSS_1_0_NAMESPACE_URI, "items");
64
65   private List modules;
66   private Map namespaceURItoModule;
67   private Map moduleToPrefix;
68
69   public RSSReader() {
70     modules = new ArrayList();
71     namespaceURItoModule = new HashMap();
72     moduleToPrefix = new HashMap();
73
74     registerModule(new RSSBasicModule(RDF_NAMESPACE_URI, "RDF module"), "rdf");
75     registerModule(new RSSBasicModule(RSS_1_0_NAMESPACE_URI, "RSS 1.0 module"), "rss");
76     registerModule(new RSSBasicModule(RSS_0_9_NAMESPACE_URI, "RSS 0.9 module"), "rss");
77
78     RSSBasicModule dcModule = new RSSBasicModule(DUBLINCORE_NAMESPACE_URI, "RSS Dublin Core 1.1");
79     dcModule.addProperty("date", RSSModule.W3CDTF_PROPERTY_TYPE);
80     registerModule(dcModule, "dc");
81
82     RSSBasicModule dcTermsModule = new RSSBasicModule(DUBLINCORE_TERMS_NAMESPACE_URI, "RSS Qualified Dublin core");
83     dcTermsModule.addProperty("created", RSSModule.W3CDTF_PROPERTY_TYPE);
84     dcTermsModule.addProperty("issued", RSSModule.W3CDTF_PROPERTY_TYPE);
85     dcTermsModule.addProperty("modified", RSSModule.W3CDTF_PROPERTY_TYPE);
86     dcTermsModule.addProperty("dateAccepted", RSSModule.W3CDTF_PROPERTY_TYPE);
87     dcTermsModule.addProperty("dateCopyrighted", RSSModule.W3CDTF_PROPERTY_TYPE);
88     dcTermsModule.addProperty("dateSubmitted", RSSModule.W3CDTF_PROPERTY_TYPE);
89     registerModule(dcTermsModule, "dcterms");
90
91     RSSBasicModule v2vTermsModule = new RSSBasicModule(V2V_NAMESPACE_URI, "indymedia v2v RSS module");
92     v2vTermsModule.addMultiValuedProperty("topic", RSSModule.PCDATA_PROPERTY_TYPE);
93     v2vTermsModule.addMultiValuedProperty("genre", RSSModule.PCDATA_PROPERTY_TYPE);
94     v2vTermsModule.addMultiValuedProperty("link", RSSModule.PCDATA_PROPERTY_TYPE);
95     registerModule(v2vTermsModule, "v2v");
96
97     registerModule(new RSSBasicModule(EVENT_NAMESPACE_URI, "Event RSS module"), "ev");
98     registerModule(new RSSBasicModule(TAXONOMY_NAMESPACE_URI, "Taxonomy RSS module"), "taxo");
99     registerModule(new RSSBasicModule(CONTENT_NAMESPACE_URI  , "Content RSS module"), "content");
100   }
101
102   public void registerModule(RSSModule aModule, String aPrefix) {
103     modules.add(aModule);
104     namespaceURItoModule.put(aModule.getNamespaceURI(), aModule);
105     moduleToPrefix.put(aModule, aPrefix);
106   }
107
108   public RSSData parseInputStream(InputStream aStream) throws RSSExc, RSSFailure {
109     try {
110       RSSData result = new RSSData();
111       XMLParserEngine.getInstance().parse("xml", aStream, new RootSectionHandler(result));
112
113       return result;
114     }
115     catch (Throwable t) {
116       throw new RSSFailure(t);
117     }
118   }
119
120   public RSSData parseInputStream(InputStream aStream, String anEncoding) throws RSSExc, RSSFailure {
121     try {
122       RSSData result = new RSSData();
123       XMLParserEngine.getInstance().parse("xml", aStream, anEncoding, new RootSectionHandler(result));
124
125       return result;
126     }
127     catch (Throwable t) {
128       throw new RSSFailure(t);
129     }
130   }
131
132   public RSSData parseUrl(String anUrl) throws RSSExc, RSSFailure {
133     try {
134       HTTPClientHelper httpClientHelper = new HTTPClientHelper();
135       InputStream inputStream = httpClientHelper.getUrl(anUrl);
136       if (inputStream==null)
137         throw new RSSExc("RSSChannel.parseUrl: Can't get url content");
138
139       RSSData theRSSData =  parseInputStream(inputStream);
140       httpClientHelper.releaseHTTPConnection();
141       return theRSSData;
142     }
143     catch (Throwable t) {
144       throw new RSSFailure(t);
145     }
146   }
147
148   public RSSData parseUrl(String anUrl, String anEncoding) throws RSSExc, RSSFailure {
149     try {
150       HTTPClientHelper httpClientHelper = new HTTPClientHelper();       
151       InputStream inputStream = httpClientHelper.getUrl(anUrl);
152       if (inputStream==null)
153         throw new RSSExc("RSSChannel.parseUrl: Can't get url content");
154
155       RSSData theRSSData =  parseInputStream(inputStream, anEncoding);
156       httpClientHelper.releaseHTTPConnection();
157       return theRSSData;
158     }
159     catch (Throwable t) {
160       throw new RSSFailure(t);
161     }
162   }
163
164   private class RootSectionHandler extends mir.util.xml.AbstractSectionHandler {
165     private RSSData data;
166
167     public RootSectionHandler(RSSData aData) {
168       data = aData;
169     }
170
171     public mir.util.xml.SectionHandler startElement(mir.util.xml.XMLName aTag, Map anAttributes) throws XMLParserExc {
172       if (aTag.getLocalName().equals("RDF")) {
173         return new RDFSectionHandler(data);
174       }
175       else
176         throw new XMLParserFailure(new RSSExc("'RDF' tag expected"));
177     };
178
179     public void endElement(mir.util.xml.SectionHandler aHandler) throws XMLParserExc {
180     };
181
182     public void characters(String aCharacters) throws XMLParserExc {
183       if (aCharacters.trim().length()>0)
184         throw new XMLParserExc("No character data allowed here");
185     };
186
187     public void finishSection() throws XMLParserExc {
188     };
189   }
190
191   private class RDFSectionHandler extends mir.util.xml.AbstractSectionHandler {
192     private RSSData data;
193
194
195     public RDFSectionHandler(RSSData aData) {
196       data = aData;
197     }
198
199     public mir.util.xml.SectionHandler startElement(mir.util.xml.XMLName aTag, Map anAttributes) throws XMLParserExc {
200       String identifier = (String) anAttributes.get(RDF_ABOUT_PARAMETER);
201       String rdfClass = makeQualifiedName(aTag);
202
203       return new RDFResourceSectionHandler(rdfClass, identifier);
204     };
205
206     public void endElement(mir.util.xml.SectionHandler aHandler) throws XMLParserExc {
207       if (aHandler instanceof RDFResourceSectionHandler) {
208         data.addResource(((RDFResourceSectionHandler) aHandler).getResource());
209       }
210     };
211
212     public void characters(String aCharacters) throws XMLParserExc {
213       if (aCharacters.trim().length()>0)
214         throw new XMLParserExc("No character data allowed here");
215     };
216
217     public void finishSection() throws XMLParserExc {
218     };
219   }
220
221   private mir.util.xml.SectionHandler makePropertyValueSectionHandler(mir.util.xml.XMLName aTag, Map anAttributes) {
222     RSSModule module = (RSSModule) namespaceURItoModule.get(aTag.getNamespaceURI());
223
224     if (module!=null) {
225       RSSModule.RSSModuleProperty property = module.getPropertyForName(aTag.getLocalName());
226
227       if (property!=null) {
228         switch (property.getType()) {
229           case
230             RSSModule.PCDATA_PROPERTY_TYPE:
231               return new PCDATASectionHandler();
232           case
233             RSSModule.RDFCOLLECTION_PROPERTY_TYPE:
234               return new RDFCollectionSectionHandler();
235 //          case
236 //            RSSModule.RDF_PROPERTY_TYPE:
237 //              return new RDFValueSectionHandler();
238           case
239             RSSModule.W3CDTF_PROPERTY_TYPE:
240               return new DateSectionHandler();
241         }
242       }
243     }
244
245     return new FlexiblePropertyValueSectionHandler();
246   }
247
248   private void usePropertyValueSectionHandler(RDFResource aResource, PropertyValueSectionHandler aHandler, mir.util.xml.XMLName aTag) {
249     RSSModule module = (RSSModule) namespaceURItoModule.get(aTag.getNamespaceURI());
250
251     if (module!=null) {
252       RSSModule.RSSModuleProperty property = module.getPropertyForName(aTag.getLocalName());
253
254       if (property!=null && property.getIsMultiValued()) {
255         List value = (List) aResource.get(makeQualifiedName(aTag));
256
257         if (value==null) {
258           value = new ArrayList();
259           aResource.set(makeQualifiedName(aTag), value);
260         }
261
262         value.add(aHandler.getValue());
263
264         return;
265       }
266     }
267
268     aResource.set(makeQualifiedName(aTag), aHandler.getValue());
269   }
270
271   private String makeQualifiedName(mir.util.xml.XMLName aName) {
272     String result=aName.getLocalName();
273     RSSModule module = (RSSModule) namespaceURItoModule.get(aName.getNamespaceURI());
274     if (module!=null) {
275       String prefix = (String) moduleToPrefix.get(module);
276
277       if (prefix!=null && prefix.length()>0)
278         result = prefix+":"+result;
279     }
280
281     return result;
282   }
283
284   private class RDFResourceSectionHandler extends mir.util.xml.AbstractSectionHandler {
285     private String image;
286     private mir.util.xml.XMLName currentTag;
287     private RDFResource resource;
288
289     public RDFResourceSectionHandler(String anRDFClass, String anIdentifier) {
290       resource = new RDFResource(anRDFClass, anIdentifier);
291     }
292
293     public mir.util.xml.SectionHandler startElement(mir.util.xml.XMLName aTag, Map anAttributes) throws XMLParserExc {
294       currentTag = aTag;
295
296       return makePropertyValueSectionHandler(aTag, anAttributes);
297     };
298
299     public void endElement(mir.util.xml.SectionHandler aHandler) throws XMLParserExc {
300       if (aHandler instanceof PropertyValueSectionHandler) {
301         usePropertyValueSectionHandler(resource, (PropertyValueSectionHandler) aHandler, currentTag);
302 //        resource.set(makeQualifiedName(currentTag), ( (PropertyValueSectionHandler) aHandler).getFieldValue());
303       }
304     };
305
306     public void characters(String aCharacters) throws XMLParserExc {
307       if (aCharacters.trim().length()>0)
308         throw new XMLParserExc("No character data allowed here");
309     };
310
311     public void finishSection() throws XMLParserExc {
312     };
313
314     public RDFResource getResource() {
315       if ((resource.getIdentifier()==null || resource.getIdentifier().length()==0) && resource.get("rss:link")!=null) {
316         resource.setIdentifier(resource.get("rss:link").toString());
317       }
318
319       return resource;
320     }
321   }
322
323   private abstract class PropertyValueSectionHandler extends mir.util.xml.AbstractSectionHandler {
324     public abstract Object getValue();
325   }
326
327   private class FlexiblePropertyValueSectionHandler extends PropertyValueSectionHandler {
328     private StringBuffer stringData;
329     private Object structuredData;
330
331     public FlexiblePropertyValueSectionHandler() {
332       stringData = new StringBuffer();
333       structuredData=null;
334     }
335
336     public mir.util.xml.SectionHandler startElement(String aTag, Map anAttributes) throws XMLParserExc {
337       if (aTag.equals(RDF_SEQUENCE_TAG))
338         return new RDFSequenceSectionHandler();
339       else
340         return new DiscardingSectionHandler();
341     };
342
343     public void endElement(mir.util.xml.SectionHandler aHandler) throws XMLParserExc {
344       if (aHandler instanceof RDFSequenceSectionHandler) {
345         structuredData= ((RDFSequenceSectionHandler) aHandler).getItems();
346       }
347     };
348
349     public void characters(String aCharacters) throws XMLParserExc {
350       stringData.append(aCharacters);
351     };
352
353     public void finishSection() throws XMLParserExc {
354     };
355
356     public String getData() {
357       return stringData.toString();
358     }
359
360     public Object getValue() {
361       if (structuredData==null)
362         return stringData.toString();
363       else
364         return structuredData;
365     }
366   }
367
368   private class RDFCollectionSectionHandler extends PropertyValueSectionHandler {
369     private List items;
370
371     public RDFCollectionSectionHandler() {
372       items = new ArrayList();
373     }
374
375     public mir.util.xml.SectionHandler startElement(String aTag, Map anAttributes) throws XMLParserExc {
376       if (aTag.equals(RDF_SEQUENCE_TAG))
377         return new RDFSequenceSectionHandler();
378       else
379         return new DiscardingSectionHandler();
380     };
381
382     public void endElement(mir.util.xml.SectionHandler aHandler) throws XMLParserExc {
383       if (aHandler instanceof RDFSequenceSectionHandler) {
384         items.addAll(((RDFSequenceSectionHandler) aHandler).getItems());
385       }
386     };
387
388     public void characters(String aCharacters) throws XMLParserExc {
389       if (aCharacters.trim().length()>0)
390         throw new XMLParserExc("No character data allowed here");
391     };
392
393     public void finishSection() throws XMLParserExc {
394     };
395
396     public List getItems() {
397       return items;
398     }
399
400     public Object getValue() {
401       return items;
402     }
403   }
404
405   private class PCDATASectionHandler extends PropertyValueSectionHandler {
406     private StringBuffer data;
407
408     public PCDATASectionHandler() {
409       data = new StringBuffer();
410     }
411
412     public mir.util.xml.SectionHandler startElement(String aTag, Map anAttributes) throws XMLParserExc {
413       throw new XMLParserFailure(new RSSExc("No subtags allowed here"));
414     };
415
416     public void endElement(mir.util.xml.SectionHandler aHandler) throws XMLParserExc {
417     };
418
419     public void characters(String aCharacters) throws XMLParserExc {
420       data.append(aCharacters);
421     };
422
423     public void finishSection() throws XMLParserExc {
424     };
425
426     public String getData() {
427       return data.toString();
428     }
429
430     public Object getValue() {
431       return data.toString();
432     }
433   }
434
435   private class DateSectionHandler extends PropertyValueSectionHandler {
436     private StringBuffer data;
437
438     public DateSectionHandler() {
439       data = new StringBuffer();
440     }
441
442     public mir.util.xml.SectionHandler startElement(String aTag, Map anAttributes) throws XMLParserExc {
443       throw new XMLParserFailure(new RSSExc("No subtags allowed here"));
444     };
445
446     public void endElement(mir.util.xml.SectionHandler aHandler) throws XMLParserExc {
447     };
448
449     public void characters(String aCharacters) throws XMLParserExc {
450       data.append(aCharacters);
451     };
452
453     public void finishSection() throws XMLParserExc {
454     };
455
456     public Object getValue() {
457       try {
458         String expression = data.toString().trim();
459
460         return DateTimeFunctions.parseW3CDTFString(expression);
461       }
462       catch (Throwable t) {
463
464         return null;
465       }
466     }
467   }
468
469
470   private class RDFSequenceSectionHandler extends mir.util.xml.AbstractSectionHandler {
471     private List items;
472
473     public RDFSequenceSectionHandler() {
474       items = new ArrayList();
475     }
476
477     public mir.util.xml.SectionHandler startElement(String aTag, Map anAttributes) throws XMLParserExc {
478       if (aTag.equals("rdf:li")) {
479         String item = (String) anAttributes.get("rdf:resource");
480
481         if (item!=null)
482           items.add(item);
483       }
484
485       return new DiscardingSectionHandler();
486     };
487
488     public void endElement(mir.util.xml.SectionHandler aHandler) throws XMLParserExc {
489     };
490
491     public void characters(String aCharacters) throws XMLParserExc {
492     };
493
494     public void finishSection() throws XMLParserExc {
495     };
496
497     public List getItems() {
498       return items;
499     }
500   }
501
502   private class RDFLiteralSectionHandler extends PropertyValueSectionHandler {
503     private StringBuffer data;
504     private String tag;
505
506     public RDFLiteralSectionHandler() {
507       data = new StringBuffer();
508     }
509
510     protected StringBuffer getData() {
511       return data;
512     }
513
514     public mir.util.xml.SectionHandler startElement(String aTag, Map anAttributes) throws XMLParserExc {
515       tag=aTag;
516       data.append("<"+tag+">");
517
518       return new RDFLiteralSectionHandler();
519     };
520
521     public void endElement(mir.util.xml.SectionHandler aHandler) throws XMLParserExc {
522       data.append(((RDFLiteralSectionHandler) aHandler).getData());
523       data.append("</"+tag+">");
524     };
525
526     public void characters(String aCharacters) throws XMLParserExc {
527       data.append(aCharacters);
528     };
529
530     public void finishSection() throws XMLParserExc {
531     };
532
533     public Object getValue() {
534       return data.toString();
535     }
536   }
537
538   private class DiscardingSectionHandler extends mir.util.xml.AbstractSectionHandler {
539     public mir.util.xml.SectionHandler startElement(String aTag, Map anAttributes) throws XMLParserExc {
540       return this;
541     };
542
543     public void endElement(mir.util.xml.SectionHandler aHandler) throws XMLParserExc {
544     };
545
546     public void characters(String aCharacters) throws XMLParserExc {
547     };
548
549     public void finishSection() throws XMLParserExc {
550     };
551   }
552 }