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