root/trunk/gregarius/extlib/rss_parse.inc

Revision 1251, 34.6 kB (checked in by sdcosta, 3 years ago)

Reverted http://svn.gregarius.net/trac/changeset/1181#file28 as per mbi's instructions...

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1<?php
2
3/**
4* Project:     MagpieRSS: a simple RSS integration tool
5* File:        rss_parse.inc  - parse an RSS or Atom feed
6*               return as a simple object.
7*
8* Handles RSS 0.9x, RSS 2.0, RSS 1.0, Atom 0.3, and Atom 1.0
9*
10* The lastest version of MagpieRSS can be obtained from:
11* http://magpierss.sourceforge.net
12*
13* For questions, help, comments, discussion, etc., please join the
14* Magpie mailing list:
15* magpierss-general@lists.sourceforge.net
16*
17* @author           Kellan Elliott-McCrea <kellan@protest.net>
18* @version          0.8
19* @license          GPL
20*
21*/
22
23define('RSS', 'RSS');
24define('ATOM', 'Atom');
25
26require_once (MAGPIE_DIR . 'rss_utils.inc');
27
28/**
29* Hybrid parser, and object, takes RSS as a string and returns a simple object.
30*
31* see: rss_fetch.inc for a simpler interface with integrated caching support
32*
33*/
34class MagpieRSS {
35    var $parser;
36   
37    var $current_item   = array();  // item currently being parsed
38    var $items          = array();  // collection of parsed items
39    var $channel        = array();  // hash of channel fields
40    var $textinput      = array();
41    var $image          = array();
42    var $feed_type;
43    var $feed_version;
44    var $encoding       = '';       // output encoding of parsed rss
45   
46    var $_source_encoding = '';     // only set if we have to parse xml prolog
47   
48    var $ERROR = "";
49    var $WARNING = "";
50   
51    // define some constants
52   
53    var $_ATOM_CONTENT_CONSTRUCTS = array(
54        'content', 'summary', 'title', /* common */
55    'info', 'tagline', 'copyright', /* Atom 0.3 */
56        'rights', 'subtitle', /* Atom 1.0 */
57    );
58    var $_XHTML_CONTENT_CONSTRUCTS = array('body', 'div');
59    var $_KNOWN_ENCODINGS    = array('UTF-8', 'US-ASCII', 'ISO-8859-1');
60
61    // parser variables, useless if you're not a parser, treat as private
62    var $stack              = array(); // parser stack
63    var $inchannel          = false;
64    var $initem             = false;
65   
66    var $incontent          = array(); // non-empty if in namespaced XML content field
67    var $exclude_top        = false; // true when Atom 1.0 type="xhtml"
68
69    var $intextinput        = false;
70    var $inimage            = false;
71    var $current_namespace  = false;
72   
73    /**
74     *  Set up XML parser, parse source, and return populated RSS object..
75     *   
76     *  @param string $source           string containing the RSS to be parsed
77     *
78     *  NOTE:  Probably a good idea to leave the encoding options alone unless
79     *         you know what you're doing as PHP's character set support is
80     *         a little weird.
81     *
82     *  NOTE:  A lot of this is unnecessary but harmless with PHP5
83     *
84     *
85     *  @param string $output_encoding  output the parsed RSS in this character
86     *                                  set defaults to ISO-8859-1 as this is PHP's
87     *                                  default.
88     *
89     *                                  NOTE: might be changed to UTF-8 in future
90     *                                  versions.
91     *                               
92     *  @param string $input_encoding   the character set of the incoming RSS source.
93     *                                  Leave blank and Magpie will try to figure it
94     *                                  out.
95     *                                 
96     *                                   
97     *  @param bool   $detect_encoding  if false Magpie won't attempt to detect
98     *                                  source encoding. (caveat emptor)
99     *
100     */
101    function MagpieRSS ($source, $output_encoding='ISO-8859-1',
102                        $input_encoding=null, $detect_encoding=true)
103    {   
104        # if PHP xml isn't compiled in, die
105        #
106        if (!function_exists('xml_parser_create')) {
107            $this->error( "Failed to load PHP's XML Extension. " .
108                          "http://www.php.net/manual/en/ref.xml.php",
109                           E_USER_ERROR );
110        }
111       
112                                # Check for a valid xml header if input encoding detection is on                 
113                                # or php's xml parser will choke                 
114                                if (($detect_encoding || $input_encoding == null) &&             
115                                        preg_match('|^\s*<\?xml[^\?]*\?>|i',$source) == 0) {             
116                                        # We don't have much of a choice here: we must force an input                   
117                                        # encoding even though it could be wrong                 
118                                        $detect_encoding = false;               
119                                        $input_encoding = "UTF-8";
120                                }
121
122        list($parser, $source) = $this->create_parser($source,
123                $output_encoding, $input_encoding, $detect_encoding);
124       
125       
126        if (!is_resource($parser)) {
127            $this->error( "Failed to create an instance of PHP's XML parser. " .
128                          "http://www.php.net/manual/en/ref.xml.php",
129                          E_USER_ERROR );
130        }
131
132       
133        $this->parser = $parser;
134       
135        # pass in parser, and a reference to this object
136        # setup handlers
137        #
138        xml_set_object( $this->parser, $this );
139        xml_set_element_handler($this->parser,
140                'feed_start_element', 'feed_end_element' );
141                       
142        xml_set_character_data_handler( $this->parser, 'feed_cdata' );
143   
144        $status = xml_parse( $this->parser, $source );
145       
146        if (! $status ) {
147            $errorcode = xml_get_error_code( $this->parser );
148            if ( $errorcode != XML_ERROR_NONE ) {
149                $xml_error = xml_error_string( $errorcode );
150                $error_line = xml_get_current_line_number($this->parser);
151                $error_col = xml_get_current_column_number($this->parser);
152                $errormsg = "$xml_error at line $error_line, column $error_col";
153
154                $this->error( $errormsg );
155            }
156        }
157       
158        xml_parser_free( $this->parser );
159
160        $this->normalize();
161    }
162   
163    function feed_start_element($p, $element, &$attrs) {
164        $el = $element = strtolower($element);
165        $attrs = array_change_key_case($attrs, CASE_LOWER);
166       
167        // check for a namespace, and split if found
168        // Don't munge content tags
169        if ( empty($this->incontent) ) {   
170                $ns = false;
171                if ( strpos( $element, ':' ) ) {
172                   list($ns, $el) = split( ':', $element, 2);
173                }
174                if ( $ns and $ns != 'rdf' ) {
175                   $this->current_namespace = $ns;
176               }
177          }
178
179        # if feed type isn't set, then this is first element of feed
180        # identify feed from root element
181        #
182        if (!isset($this->feed_type) ) {
183            if ( $el == 'rdf' ) {
184                $this->feed_type = RSS;
185                $this->feed_version = '1.0';
186            }
187            elseif ( $el == 'rss' ) {
188                $this->feed_type = RSS;
189                $this->feed_version = $attrs['version'];
190            }
191            elseif ( $el == 'feed' ) {
192                $this->feed_type = ATOM;
193                    if ($attrs['xmlns'] == 'http://www.w3.org/2005/Atom') { // Atom 1.0
194                        $this->feed_version = '1.0';
195                    }
196                    else { // Atom 0.3, probably.
197                        $this->feed_version = $attrs['version'];
198                    }
199                $this->inchannel = true;
200            }
201            return;
202        }
203   
204        // if we're inside a namespaced content construct, treat tags as text
205        if ( !empty($this->incontent) )
206        {
207                if ((count($this->incontent) > 1) or !$this->exclude_top) {
208                      // if tags are inlined, then flatten
209                      $attrs_str = join(' ',
210                          array_map('map_attrs',
211                          array_keys($attrs),
212                          array_values($attrs) )
213                        );
214                   
215                        if (strlen($attrs_str) > 0) { $attrs_str = ' '.$attrs_str; }
216       
217                        $this->append_content( "<{$element}{$attrs_str}>"  );
218                }
219                array_push($this->incontent, $el); // stack for parsing content XML
220        }
221       
222        elseif ( $el == 'channel' )  {
223            $this->inchannel = true;
224        }
225   
226        elseif ($el == 'item' or $el == 'entry' )
227        {
228            $this->initem = true;
229            if ( isset($attrs['rdf:about']) ) {
230                $this->current_item['about'] = $attrs['rdf:about'];
231            }
232        }
233
234        // if we're in the default namespace of an RSS feed,
235        //  record textinput or image fields
236        elseif (
237            $this->feed_type == RSS and
238            $this->current_namespace == '' and
239            $el == 'textinput' )
240        {
241            $this->intextinput = true;
242        }
243
244        // This is a hack to ignore the text input.
245        // --Sameer
246        elseif (
247            $this->feed_type == RSS and
248            $this->current_namespace == '' and
249            ($this->intextinput or $this->inimage))
250        {
251
252        }
253
254        elseif (
255            $this->feed_type == RSS and
256            $this->current_namespace == '' and
257            $el == 'image' )
258        {
259            $this->inimage = true;
260        }
261       
262        // set stack[0] to current element
263        else {
264              // Atom support many links per containing element.
265              // Magpie treats link elements of type rel='alternate'
266              // as being equivalent to RSS's simple link element.
267
268              $atom_link = false;
269              if ($this->feed_type == ATOM and $el == 'link') {
270                    $atom_link = true;
271                    if (isset($attrs['rel']) and $attrs['rel'] != 'alternate') {
272                          $el = $el . "_" . $attrs['rel'];  // pseudo-element names for Atom link elements
273                    }
274              }
275              # handle atom content constructs
276              elseif ( $this->feed_type == ATOM and in_array($el, $this->_ATOM_CONTENT_CONSTRUCTS) )
277              {
278                    // avoid clashing w/ RSS mod_content
279                    if ($el == 'content' ) {
280                          $el = 'atom_content';
281                    }
282
283                    // assume that everything accepts namespaced XML
284                    // (that will pass through some non-validating feeds;
285                    // but so what? this isn't a validating parser)
286                    $this->incontent = array();
287                    array_push($this->incontent, $el); // start a stack
288
289                    if ( isset($attrs['type']) and trim(strtolower($attrs['type']))=='xhtml') {
290                        $this->exclude_top = true;
291                    } else {
292                        $this->exclude_top = false;
293                    }
294              }
295              # Handle inline XHTML body elements --CWJ
296              elseif (($this->current_namespace=='xhtml' or
297                        (isset($attrs['xmlns']) and $attrs['xmlns'] == 'http://www.w3.org/1999/xhtml'))
298                      and in_array($el, $this->_XHTML_CONTENT_CONSTRUCTS) )
299              {
300                    $this->current_namespace = 'xhtml';
301                    $this->incontent = array();
302                    array_push($this->incontent, $el); // start a stack
303                    $this->exclude_top = false;
304              }
305
306              array_unshift($this->stack, $el);
307              $elpath = join('_', array_reverse($this->stack));
308       
309              $n = $this->element_count($elpath);
310              $this->element_count($elpath, $n+1);
311       
312              if ($n > 0) {
313                  array_shift($this->stack);
314                  array_unshift($this->stack, $el.'#'.($n+1));
315                  $elpath = join('_', array_reverse($this->stack));
316              }
317
318              // this makes the baby Jesus cry, but we can't do it in normalize()
319              // because we've made the element name for Atom links unpredictable
320              // by tacking on the relation to the end. -CWJ
321              if ($atom_link and isset($attrs['href'])) {
322                    $this->append($elpath, $attrs['href']);
323              }
324       
325              // add attributes
326              if (count($attrs) > 0) {
327                    $this->append($elpath.'@', join(',', array_keys($attrs)));
328                    foreach ($attrs as $attr => $value) {
329                         $this->append($elpath.'@'.$attr, $value);
330                    }
331              }
332       }
333    }
334   
335
336   
337    function feed_cdata ($p, $text) {
338       
339        if ($this->incontent) {
340            $this->append_content( $text );
341        }
342        else {
343            $current_el = join('_', array_reverse($this->stack));
344            $this->append($current_el, $text);
345        }
346    }
347   
348    function feed_end_element ($p, $el) {
349        $el = strtolower($el);
350
351        if ( $this->incontent ) {
352        $opener = array_pop($this->incontent);
353
354        // Don't get bamboozled by namespace voodoo
355        if (strpos($el, ':')) { list($ns, $closer) = split(':', $el); }
356        else { $ns = false; $closer = $el; }
357
358        // Don't get bamboozled by our munging of <atom:content>, either
359        if ($this->feed_type == ATOM and $closer == 'content') {
360            $closer = 'atom_content';
361        }
362
363        // balance tags properly
364        // note:  i don't think this is actually neccessary
365        if ($opener != $closer) {
366            array_push($this->incontent, $opener);
367            $this->append_content("<$el />");
368        } elseif ($this->incontent) { // are we in the content construct still?
369            if ((count($this->incontent) > 1) or !$this->exclude_top) {
370                $this->append_content("</$el>");
371            }
372        } else { // shift the opening of the content construct off the normal stack
373            array_shift( $this->stack );
374        }
375        }
376        elseif ( $el == 'item' or $el == 'entry' )
377        {
378            $this->items[] = $this->current_item;
379            $this->current_item = array();
380            $this->initem = false;
381
382        $this->current_category = 0;
383        }
384       elseif ($this->feed_type == RSS and $this->current_namespace == '' and $el == 'textinput' )
385        {
386            $this->intextinput = false;
387        }
388        elseif ($this->feed_type == RSS and $this->current_namespace == '' and $el == 'image' )
389        {
390            $this->inimage = false;
391        }
392        elseif ($el == 'channel' or $el == 'feed' )
393        {
394            $this->inchannel = false;
395        }
396        else {
397        array_shift( $this->stack );
398        }
399       
400    if ( !$this->incontent ) { // Don't munge the namespace after finishing with elements in namespaced content constructs -CWJ
401        $this->current_namespace = false;
402    }
403    }
404   
405    function concat (&$str1, $str2="") {
406        if (!isset($str1) ) {
407            $str1="";
408        }
409        $str1 .= $str2;
410    }
411   
412    function append_content($text) {
413    if ( $this->initem ) {
414        if ($this->current_namespace) {
415            $this->concat( $this->current_item[$this->current_namespace][ reset($this->incontent) ], $text );
416        } else {
417            $this->concat( $this->current_item[ reset($this->incontent) ], $text );
418        }
419    }
420    elseif ( $this->inchannel ) {
421        if ($this->current_namespace) {
422            $this->concat( $this->channel[$this->current_namespace][ reset($this->incontent) ], $text );
423        } else {
424            $this->concat( $this->channel[ reset($this->incontent) ], $text );
425        }
426    }
427    }
428   
429    // smart append - field and namespace aware
430    function append($el, $text) {
431        if (!$el) {
432            return;
433        }
434        if ( $this->current_namespace )
435        {
436            if ( $this->initem ) {
437                                                        $real_element = ""; 
438                                                 
439                                                        $element_tree = explode("_", $el); 
440                                                        $real_element =& $this->current_item[ $this->current_namespace ]; 
441       
442                                                        foreach ($element_tree as $tree_element) { 
443                                                                        if (!is_array($real_element)) { 
444                                                                                        $real_element = array(); 
445                                                                        } 
446       
447                                                                        $real_element =& $real_element[$tree_element]; 
448                                                        } 
449                                                       
450
451                $this->concat($real_element, $text);
452            }
453            elseif ($this->inchannel) {
454        $this->concat(
455            $this->channel[ $this->current_namespace][ $el ], $text );
456        }
457            elseif ($this->intextinput) {
458                $this->concat(
459                    $this->textinput[ $this->current_namespace][ $el ], $text );
460            }
461            elseif ($this->inimage) {
462                $this->concat(
463                    $this->image[ $this->current_namespace ][ $el ], $text );
464            }
465        }
466        else {
467            if ( $this->initem ) {
468        $this->concat(
469            $this->current_item[ $el ], $text);
470            }
471            elseif ($this->intextinput) {
472                $this->concat(
473                    $this->textinput[ $el ], $text );
474            }
475            elseif ($this->inimage) {
476                $this->concat(
477                    $this->image[ $el ], $text );
478            }
479            elseif ($this->inchannel) {
480        $this->concat(
481            $this->channel[ $el ], $text );
482            }
483           
484        }
485    }
486
487    // smart count - field and namespace aware
488    function element_count ($el, $set = NULL) {
489        if (!$el) {
490            return;
491        }
492        if ( $this->current_namespace )
493        {
494            if ( $this->initem ) {
495            if (!is_null($set)) { $this->current_item[ $this->current_namespace ][ $el.'#' ] = $set; }
496            $ret = (isset($this->current_item[ $this->current_namespace ][ $el.'#' ]) ?
497            $this->current_item[ $this->current_namespace ][ $el.'#' ] : 0);
498            }
499            elseif ($this->inchannel) {
500            if (!is_null($set)) { $this->channel[ $this->current_namespace ][ $el.'#' ] = $set; }
501            $ret = (isset($this->channel[ $this->current_namespace][ $el.'#' ]) ?
502            $this->channel[ $this->current_namespace][ $el.'#' ] : 0);
503        }
504        }
505        else {
506            if ( $this->initem ) {
507            if (!is_null($set)) { $this->current_item[ $el.'#' ] = $set; }
508            $ret = (isset($this->current_item[ $el.'#' ]) ?
509            $this->current_item[ $el.'#' ] : 0);
510            }
511            elseif ($this->inchannel) {
512            if (!is_null($set)) {$this->channel[ $el.'#' ] = $set; }
513            $ret = (isset($this->channel[ $el.'#' ]) ?
514            $this->channel[ $el.'#' ] : 0);
515        }
516        }
517    return $ret;
518    }
519
520    function normalize_enclosure (&$source, $from, &$dest, $to, $i) {
521        $id_from = $this->element_id($from, $i);
522        $id_to = $this->element_id($to, $i);
523        if (isset($source["{$id_from}@"])) {
524            foreach (explode(',', $source["{$id_from}@"]) as $attr) {
525                if ($from=='link_enclosure' and $attr=='href') { // from Atom
526                    $dest["{$id_to}@url"] = $source["{$id_from}@{$attr}"];
527                    $dest["{$id_to}"] = $source["{$id_from}@{$attr}"];
528                }
529                elseif ($from=='enclosure' and $attr=='url') { // from RSS
530                    $dest["{$id_to}@href"] = $source["{$id_from}@{$attr}"];
531                    $dest["{$id_to}"] = $source["{$id_from}@{$attr}"];
532                }
533                else {
534                    $dest["{$id_to}@{$attr}"] = $source["{$id_from}@{$attr}"];
535                }
536            }
537        }
538    }
539
540    function normalize_atom_person (&$source, $person, &$dest, $to, $i) {
541        $id = $this->element_id($person, $i);
542        $id_to = $this->element_id($to, $i);
543
544            // Atom 0.3 <=> Atom 1.0
545        if ($this->feed_version >= 1.0) { $used = 'uri'; $norm = 'url'; }
546        else { $used = 'url'; $norm = 'uri'; }
547
548        if (isset($source["{$id}_{$used}"])) {
549            $dest["{$id_to}_{$norm}"] = $source["{$id}_{$used}"];
550        }
551
552        // Atom to RSS 2.0 and Dublin Core
553        // RSS 2.0 person strings should be valid e-mail addresses if possible.
554        if (isset($source["{$id}_email"])) {
555            $rss_author = $source["{$id}_email"];
556        }
557        if (isset($source["{$id}_name"])) {
558            $rss_author = $source["{$id}_name"]
559                . (isset($rss_author) ? " <$rss_author>" : '');
560        }
561        if (isset($rss_author)) {
562            $source[$id] = $rss_author; // goes to top-level author or contributor
563        $dest[$id_to] = $rss_author; // goes to dc:creator or dc:contributor
564        }
565    }
566
567    // Normalize Atom 1.0 and RSS 2.0 categories to Dublin Core...
568    function normalize_category (&$source, $from, &$dest, $to, $i) {
569        $cat_id = $this->element_id($from, $i);
570        $dc_id = $this->element_id($to, $i);
571
572        // first normalize category elements: Atom 1.0 <=> RSS 2.0
573        if ( isset($source["{$cat_id}@term"]) ) { // category identifier
574            $source[$cat_id] = $source["{$cat_id}@term"];
575        } elseif ( $this->feed_type == RSS ) {
576            $source["{$cat_id}@term"] = $source[$cat_id];
577        }
578       
579        if ( isset($source["{$cat_id}@scheme"]) ) { // URI to taxonomy
580            $source["{$cat_id}@domain"] = $source["{$cat_id}@scheme"];
581        } elseif ( isset($source["{$cat_id}@domain"]) ) {
582            $source["{$cat_id}@scheme"] = $source["{$cat_id}@domain"];
583        }
584
585        // Now put the identifier into dc:subject
586        $dest[$dc_id] = $source[$cat_id];
587    }
588   
589    // ... or vice versa
590    function normalize_dc_subject (&$source, $from, &$dest, $to, $i) {
591        $dc_id = $this->element_id($from, $i);
592        $cat_id = $this->element_id($to, $i);
593
594        $dest[$cat_id] = $source[$dc_id];       // RSS 2.0
595        $dest["{$cat_id}@term"] = $source[$dc_id];  // Atom 1.0
596    }
597
598    // simplify the logic for normalize(). Makes sure that count of elements and
599    // each of multiple elements is normalized properly. If you need to mess
600    // with things like attributes or change formats or the like, pass it a
601    // callback to handle each element.
602    function normalize_element (&$source, $from, &$dest, $to, $via = NULL) {
603        if (isset($source[$from]) or isset($source["{$from}#"])) {
604            if (isset($source["{$from}#"])) {
605                $n = $source["{$from}#"];
606                $dest["{$to}#"] = $source["{$from}#"];
607            }
608            else { $n = 1; }
609
610            for ($i = 1; $i <= $n; $i++) {
611                if (isset($via)) { // custom callback for ninja attacks
612                    $this->{$via}($source, $from, $dest, $to, $i);
613                }
614                else { // just make it the same
615                    $from_id = $this->element_id($from, $i);
616                    $to_id = $this->element_id($to, $i);
617                    $dest[$to_id] = $source[$from_id];
618                }
619            }
620        }
621    }
622
623    function normalize () {
624        // if atom populate rss fields and normalize 0.3 and 1.0 feeds
625        if ( $this->is_atom() ) {
626        // Atom 1.0 elements <=> Atom 0.3 elements (Thanks, o brilliant wordsmiths of the Atom 1.0 standard!)
627        if ($this->feed_version < 1.0) {
628            $this->normalize_element($this->channel, 'tagline', $this->channel, 'subtitle');
629            $this->normalize_element($this->channel, 'copyright', $this->channel, 'rights');
630            $this->normalize_element($this->channel, 'modified', $this->channel, 'updated');
631        } else {
632            $this->normalize_element($this->channel, 'subtitle', $this->channel, 'tagline');
633            $this->normalize_element($this->channel, 'rights', $this->channel, 'copyright');
634            $this->normalize_element($this->channel, 'updated', $this->channel, 'modified');
635        }
636        $this->normalize_element($this->channel, 'author', $this->channel['dc'], 'creator', 'normalize_atom_person');
637        $this->normalize_element($this->channel, 'contributor', $this->channel['dc'], 'contributor', 'normalize_atom_person');
638
639        // Atom elements to RSS elements
640        $this->normalize_element($this->channel, 'subtitle', $this->channel, 'description');
641       
642        if ( isset($this->channel['logo']) ) {
643            $this->normalize_element($this->channel, 'logo', $this->image, 'url');
644            $this->normalize_element($this->channel, 'link', $this->image, 'link');
645            $this->normalize_element($this->channel, 'title', $this->image, 'title');
646        }
647
648        for ( $i = 0; $i < count($this->items); $i++) {
649            $item = $this->items[$i];
650
651            // Atom 1.0 elements <=> Atom 0.3 elements
652            if ($this->feed_version < 1.0) {
653                $this->normalize_element($item, 'modified', $item, 'updated');
654                $this->normalize_element($item, 'issued', $item, 'published');
655            } else {
656                $this->normalize_element($item, 'updated', $item, 'modified');
657                $this->normalize_element($item, 'published', $item, 'issued');
658            }
659
660            // "If an atom:entry element does not contain
661            // atom:author elements, then the atom:author elements
662            // of the contained atom:source element are considered
663            // to apply. In an Atom Feed Document, the atom:author
664            // elements of the containing atom:feed element are
665            // considered to apply to the entry if there are no
666            // atom:author elements in the locations described
667            // above." <http://atompub.org/2005/08/17/draft-ietf-atompub-format-11.html#rfc.section.4.2.1>
668            if (!isset($item["author#"])) {
669                if (isset($item["source_author#"])) { // from aggregation source
670                    $source = $item;
671                    $author = "source_author";
672                } elseif (isset($this->channel["author#"])) { // from containing feed
673                    $source = $this->channel;
674                    $author = "author";
675                }
676
677                $item["author#"] = $source["{$author}#"];
678                for ($au = 1; $au <= $item["author#"]; $au++) {
679                    $id_to = $this->element_id('author', $au);
680                    $id_from = $this->element_id($author, $au);
681                   
682                    $item[$id_to] = $source[$id_from];
683                    foreach (array('name', 'email', 'uri', 'url') as $what) {
684                        if (isset($source["{$id_from}_{$what}"])) {
685                            $item["{$id_to}_{$what}"] = $source["{$id_from}_{$what}"];
686                        }
687                    }
688                }
689            }
690
691            // Atom elements to RSS elements
692            $this->normalize_element($item, 'author', $item['dc'], 'creator', 'normalize_atom_person');
693            $this->normalize_element($item, 'contributor', $item['dc'], 'contributor', 'normalize_atom_person');
694            $this->normalize_element($item, 'summary', $item, 'description');
695            $this->normalize_element($item, 'atom_content', $item['content'], 'encoded');
696            $this->normalize_element($item, 'link_enclosure', $item, 'enclosure', 'normalize_enclosure');
697
698            // Categories
699            if ( isset($item['category#']) ) { // Atom 1.0 categories to dc:subject and RSS 2.0 categories
700                $this->normalize_element($item, 'category', $item['dc'], 'subject', 'normalize_category');
701            }
702            elseif ( isset($item['dc']['subject#']) ) { // dc:subject to Atom 1.0 and RSS 2.0 categories
703                $this->normalize_element($item['dc'], 'subject', $item, 'category', 'normalize_dc_subject');
704            }
705
706            // Normalized item timestamp
707            $atom_date = (isset($item['published']) ) ? $item['published'] : $item['updated'];
708            if ( $atom_date ) {
709                $epoch = @parse_w3cdtf($atom_date);
710                if ($epoch and $epoch > 0) {
711                    $item['date_timestamp'] = $epoch;
712                }
713            }
714
715            $this->items[$i] = $item;
716        }
717        }
718        elseif ( $this->is_rss() ) {
719        // RSS elements to Atom elements
720        $this->normalize_element($this->channel, 'description', $this->channel, 'tagline'); // Atom 0.3
721        $this->normalize_element($this->channel, 'description', $this->channel, 'subtitle'); // Atom 1.0 (yay wordsmithing!)
722        $this->normalize_element($this->image, 'url', $this->channel, 'logo');
723
724            for ( $i = 0; $i < count($this->items); $i++) {
725                $item = $this->items[$i];
726       
727        // RSS elements to Atom elements
728        $this->normalize_element($item, 'description', $item, 'summary');
729                $this->normalize_element($item['content'], 'encoded', $item, 'atom_content');
730        $this->normalize_element($item, 'enclosure', $item, 'link_enclosure', 'normalize_enclosure');
731
732        // Categories
733        if ( isset($item['category#']) ) { // RSS 2.0 categories to dc:subject and Atom 1.0 categories
734            $this->normalize_element($item, 'category', $item['dc'], 'subject', 'normalize_category');
735        }
736        elseif ( isset($item['dc']['subject#']) ) { // dc:subject to Atom 1.0 and RSS 2.0 categories
737            $this->normalize_element($item['dc'], 'subject', $item, 'category', 'normalize_dc_subject');
738        }
739
740        // Normalized item timestamp
741                if ( $this->is_rss() == '1.0' and isset($item['dc']['date']) ) {
742                    $epoch = @parse_w3cdtf($item['dc']['date']);
743                    if ($epoch and $epoch > 0) {
744                        $item['date_timestamp'] = $epoch;
745                    }
746                }
747                elseif ( isset($item['pubdate']) ) {
748                    $epoch = @strtotime($item['pubdate']);
749                    if ($epoch > 0) {
750                        $item['date_timestamp'] = $epoch;
751                    }
752                }
753
754                $this->items[$i] = $item;
755            }
756        }
757    }
758   
759   
760    function is_rss () {
761        if ( $this->feed_type == RSS ) {
762            return $this->feed_version;
763        }
764        else {
765            return false;
766        }
767    }
768   
769    function is_atom() {
770        if ( $this->feed_type == ATOM ) {
771            return $this->feed_version;
772        }
773        else {
774            return false;
775        }
776    }
777
778    /**
779    * return XML parser, and possibly re-encoded source
780    *
781    */
782    function create_parser($source, $out_enc, $in_enc, $detect) {
783        if ( substr(phpversion(),0,1) == 5) {
784            $parser = $this->php5_create_parser($in_enc, $detect);
785        }
786        else {
787            list($parser, $source) = $this->php4_create_parser($source, $in_enc, $detect);
788        }
789        if ($out_enc) {
790            $this->encoding = $out_enc;
791            xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, $out_enc);
792        }
793       
794        return array($parser, $source);
795    }
796   
797    /**
798    * Instantiate an XML parser under PHP5
799    *
800    * PHP5 will do a fine job of detecting input encoding
801    * if passed an empty string as the encoding.
802    *
803    * All hail libxml2!
804    *
805    */
806    function php5_create_parser($in_enc, $detect) {
807        // by default php5 does a fine job of detecting input encodings
808        if(!$detect && $in_enc) {
809            return xml_parser_create($in_enc);
810        }
811        else {
812            return xml_parser_create('');
813        }
814    }
815   
816    /**
817    * Instaniate an XML parser under PHP4
818    *
819    * Unfortunately PHP4's support for character encodings
820    * and especially XML and character encodings sucks.  As
821    * long as the documents you parse only contain characters
822    * from the ISO-8859-1 character set (a superset of ASCII,
823    * and a subset of UTF-8) you're fine.  However once you
824    * step out of that comfy little world things get mad, bad,
825    * and dangerous to know.
826    *
827    * The following code is based on SJM's work with FoF
828    * @see http://minutillo.com/steve/weblog/2004/6/17/php-xml-and-character-encodings-a-tale-of-sadness-rage-and-data-loss
829    *
830    */
831    function php4_create_parser($source, $in_enc, $detect) {
832        if ( !$detect ) {
833            return array(xml_parser_create($in_enc), $source);
834        }
835       
836        if (!$in_enc) {
837            if (preg_match('/<?xml.*encoding=[\'"](.*?)[\'"].*?>/m', $source, $m)) {
838                $in_enc = strtoupper($m[1]);
839                $this->source_encoding = $in_enc;
840            }
841            else {
842                $in_enc = 'UTF-8';
843            }
844        }
845       
846        if ($this->known_encoding($in_enc)) {
847            return array(xml_parser_create($in_enc), $source);
848        }
849       
850        // the dectected encoding is not one of the simple encodings PHP knows
851       
852        // attempt to use the iconv extension to
853        // cast the XML to a known encoding
854        // @see http://php.net/iconv
855       
856        if (function_exists('iconv'))  {
857            $encoded_source = iconv($in_enc,'UTF-8', $source);
858            if ($encoded_source) {
859                return array(xml_parser_create('UTF-8'), $encoded_source);
860            }
861        }
862       
863        // iconv didn't work, try mb_convert_encoding
864        // @see http://php.net/mbstring
865        if(function_exists('mb_convert_encoding')) {
866            $encoded_source = mb_convert_encoding($source, 'UTF-8', $in_enc );
867            if ($encoded_source) {
868                return array(xml_parser_create('UTF-8'), $encoded_source);
869            }
870        }
871       
872        // else
873        $this->error("Feed is in an unsupported character encoding. ($in_enc) " .
874                     "You may see strange artifacts, and mangled characters.",
875                     E_USER_NOTICE);
876           
877        return array(xml_parser_create(), $source);
878    }
879   
880    function known_encoding($enc) {
881        $enc = strtoupper($enc);
882        if ( in_array($enc, $this->_KNOWN_ENCODINGS) ) {
883            return $enc;
884        }
885        else {
886            return false;
887        }
888    }
889
890    function error ($errormsg, $lvl=E_USER_WARNING) {
891        // append PHP's error message if track_errors enabled
892        if ( isset($php_errormsg) ) {
893            $errormsg .= " ($php_errormsg)";
894        }
895        if ( MAGPIE_DEBUG ) {
896            trigger_error( $errormsg, $lvl);       
897        }
898        else {
899            error_log( $errormsg, 0);
900        }
901       
902        $notices = E_USER_NOTICE|E_NOTICE;
903        if ( $lvl&$notices ) {
904            $this->WARNING = $errormsg;
905        } else {
906            $this->ERROR = $errormsg;
907        }
908    }
909
910    // magic ID function for multiple elemenets.
911    // can be called as static MagpieRSS::element_id()
912    function element_id ($el, $counter) {
913        return $el . (($counter > 1) ? '#'.$counter : '');
914    }
915} // end class RSS
916
917function map_attrs($k, $v) {
918    return "$k=\"$v\"";
919}
920
921// patch to support medieval versions of PHP4.1.x,
922// courtesy, Ryan Currie, ryan@digibliss.com
923
924if (!function_exists('array_change_key_case')) {
925    define("CASE_UPPER",1);
926    define("CASE_LOWER",0);
927
928
929    function array_change_key_case($array,$case=CASE_LOWER) {
930       if ($case==CASE_LOWER) $cmd='strtolower';
931       elseif ($case==CASE_UPPER) $cmd='strtoupper';
932       foreach($array as $key=>$value) {
933               $output[$cmd($key)]=$value;
934       }
935       return $output;
936    }
937
938}
939
940?>
Note: See TracBrowser for help on using the browser.