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