[ create a new paste ] login | about

Link: http://codepad.org/Ncs22ZkY    [ raw code | fork ]

k4st - PHP, pasted on May 29:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
<?php

error_reporting(E_ALL | E_STRICT);

/**
 * Exception class for the XmlPrinter class.
 * @author Peter Goodman
 */
class XmlPrinterException extends Exception { }

/**
 * A simple stack-based XML string constructor for creating XML.
 * @author Peter Goodman
 */
class XmlPrinter {
    
    // what type of tag is this?
    const TAG_CLOSING = 1,
          TAG_NON_CLOSING = 0;

    // exception error codes
    const ERROR_UNRECOVERABLE = 1, // these are usually programmer errors that
          ERROR_EMPTY_STACK = 2,   // need to be fixed in the code
          ERROR_UNEXPECTED_TAG = 4,
          ERROR_UNFINISHED_DOCUMENT = 8;
    
    // stack of tags and the current indent level
    private $tags = array(),
            $indent = -1;
    
    /**
     * Construct the class and put in the base <xml> tag. This tag is non-
     * removable from the stack and isn't built in the usual way :) It's an
     * exception to the rule.
     */
    public function __construct() { 
        $this->open(' ');
        $xml =& $this->tags[$this->indent];
        
        $xml['children'] = -1;
        $xml['content'] = '<?xml version="1.0"?>';
        $this->indent = -1;
    }
    
    // destructor, obvious.
    public function __destruct() {
        unset($this->stack);
    }
    
    /**
     * Add some content to the current tag on the top of the stack.
     */
    public function hasContent($content = "") {
        
        $tag =& $this->tags[$this->indent+1];
        $nl = FALSE === strstr($content, "\n") ? '' : "\n";
        
        // deal with ampersands
        $content = preg_replace("~&(?!amp)~", "&amp;", $content);
        
        // wrap the content in a CDATA block if it contains tags
        if(preg_match("~[<>]~i", $content))
            $content = "<![CDATA[{$nl}{$content}{$nl}]]>";
        
        // what should the indent prefix be?
        $prefix = str_repeat("\t", $this->indent+1);
        
        // prefix the content
        $c = "";
        foreach(explode("\n", $content) as $line) {
            if(strlen($line) > 0) {
                $tag['content_num_lines']++;
                $c .= $nl . $prefix . $line;
            }
        }
        
        // only one line, lets remove the prefix
        if($tag['content_num_lines'] == 1)
            $c = substr($c, strlen($prefix));
        
        $tag['content'] .= $c;
        
        return $this;
    }
    
    /**
     * Push a tag onto the XML tag stack.
     */
    public function open($name = '', $type = self::TAG_CLOSING) {
        
        // we can't push no tag onto the tag stack
        if(empty($name) || !is_string($name))
            throw new XmlPrinterException("Tag name expected in push operation.",
                                           self::ERROR_UNRECOVERABLE);
        
        $this->indent++;
        $this->tags[] = array('name' => $name,
                              'closing' => (bool)$type,
                              'attr' => '',
                              'content' => '',
                              'children' => 0,
                              'content_num_lines' => 0);
        
        // get the parent tag and tell it that it has one more child. The
        // children is also incremented in the pop method; however, these
        // don't conflict because the lowest level child will have 0 children
        // and this will have already been called. Thus, 1 + 0 accurately
        // represents the number of children the node at the top of the stack
        // has when a leaf node is popped, and so it all bubbles up nicely.
        $parent =& $this->tags[$this->indent];
        $parent['children']++;
        
        return $this;
    }
    
    /**
     * Pop the current tag off of the tag stack.
     */
    public function close($expect = NULL) {
        
        // we've closed all tags, this is a guard against popping the xml
        // tag off the stack
        if($this->indent < 0) {
            $extra = !empty($expect) ? self::ERROR_UNRECOVERABLE : 0;
            throw new XmlPrinterException("No tags left to close.",
                                           self::ERROR_EMPTY_STACK | $extra);
        }
        
        // get the tag that we're popping off the stack
        $tag = array_pop($this->tags);
        
        // did we expect to close a certain tag name and it's not the same one
        // that the class expects?
        if($expect !== NULL && $tag['name'] != $expect)
            throw new XmlPrinterException("Malformed XML. Expected [{$expect}] " 
                                          ."but found [{$tag['name']}].",
                                          self::ERROR_UNEXPECTED_TAG);
    
        // this builds the output from the inside out by putting the content
        // into the parent tag's content.
        $prefix = $this->indent > 0 ? str_repeat("\t", $this->indent) : '';
        $suffix = !$tag['closing'] ? ' /' : '';
        $content = "\n{$prefix}<". $tag['name'] . $tag['attr'] . $suffix .">";
        
        // do we need to build the contents of this tag?
        if($tag['closing']) {
            
            // write this tag over multiple lines
            if($tag['content_num_lines'] > 1 || $tag['children'] > 0)
                $content .= "{$prefix}". $tag['content'] ."\n{$prefix}";

            // write this tag over a single line
            else
                $content .= $tag['content'];
                
            $content .= '</'. $tag['name'] .">";
        }
        
        // add stuff into the parent and modify its content and children
        // counter
        $parent =& $this->tags[$this->indent];
        $parent['content'] .= $content;
        $parent['children'] += $tag['children'];
        
        $this->indent--;
        
        return $this;
    }
    
    /**
     * Append an attribute to the top tag in the stack. This is really just a
     * convenient shortcut.
     */
    public function __call($attr, array $args = array()) {
        
        if(empty($args))
            $args = array("");
        
        return $this->hasAttr($attr, (string)$args[0]);
    }

    /**
     * Secondary function for attributes if an attribute, for example, has a 
     * colon in its name or has the name push, pop, or one of the other names
     * of one of this class' functions.
     */
    public function hasAttr($attr, $val = '') {

        // unrecoverable programmer error, attributes need to have a name
        if(empty($attr))
            throw new XmlPrinterException("XML tag attribute must has a name.",
                                           self::ERROR_UNRECOVERABLE);
        
        $tag =& $this->tags[$this->indent+1];

        // clean up any quotes in the attribute value so that they don't
        // conflict with. Single quotes are allowed to pass because the
        // attribute value is delimited with doubles
        $attr = (string)$attr;
        $val = preg_replace('~[\\\]*"~', '\"', (string)$val);
        $tag['attr'] .= " {$attr}=\"{$val}\"";
        
        return $this;
    }
    
    /**
     * Do the header call.
     */
    public function doHeader() {
        header("Content-Type: text/xml");
    }
    
    /**
     * Return the built XML string.
     */
    public function __toString() {
        
        // whoops, we're not done building the xml document yet!
        if($this->indent > 0)
            throw new XmlPrinterException("Cannot export malformed XML document.",
                                           self::ERROR_UNFINISHED_DOCUMENT);
        
        return (string)$this->tags[0]['content'];
    }
}

/**
 * RSS Feed Printer.
 * @author Peter Goodman
 */
class RssFeedPrinter extends XmlPrinter {
    
    // some other helpful stuff
    const DATE_FORMAT = "D, d M Y H:i:s T";
    
    private $done = FALSE, // are we done building the feed?
            $context = 'rss', // what is the parent tag?
            
            // obvious
            $required_tags = array('rss'     => array('channel' => FALSE),
                                   'channel' => array('title' => FALSE,
                                                      'link' => FALSE,
                                                      'description' => FALSE),
                                   'item'    => array());
    
    /**
     * Construct and set up the rss top level tag.
     */
    public function __construct() {
        parent::__construct();
        $this->open('rss')->version("2.0")
             ->hasAttr("xmlns:atom", "http://www.w3.org/2005/Atom");
        
        $this->context = 'rss';
    }
    
    /**
     * Open the <channel> tag, set up the atom:link, and set the context.
     */
    public function open_channel() {
        $this->open('channel');
        $this->open('atom:link', XmlPrinter::TAG_NON_CLOSING)
                    ->href(self::encodeURL())->rel("self")
                    ->type("application/rss+xml")->close();
        
        // if we have multiple channels in one feed
        foreach($this->required_tags['channel'] as $key => $val)
            $this->required_tags['channel'][$key] = FALSE;
        
        // set the context
        $this->context = 'channel';
        
        return $this;
    }
    
    /**
     * Create the </channel>
     */
    public function close_channel() {
        $this->close('channel');
        $this->checkContext();
        $this->context = 'rss';
        return $this;
    }
    
    /**
     * Create the <item> and set the context.
     */
    public function open_item() {
        $this->open('item');
        $this->context = 'item';
        return $this;
    }
    
    /**
     * Create the </item>.
     */
    public function close_item() {
        $this->close('item');
        $this->context = 'channel';
        return $this;
    }
    
    /**
     * Print out the RSS.
     */
    public function __toString() {
        if(!$this->done)
            $this->close('rss');
        
        $this->done = TRUE;
        $this->context = 'rss';
        $this->checkContext();
        
        return parent::__toString();
    }
    
    /**
     * Open a tag, if it's one of the required tags for this context then
     * mark it off that we've met that requirement.
     */
    public function open($name = '', $type = parent::TAG_CLOSING) {
        
        // check off this required tag
        if(isset($this->required_tags[$this->context][$name]))
            $this->required_tags[$this->context][$name] = TRUE;
        
        return parent::open($name, $type);
    }
    
    /**
     * Check if all required tags have been used in this context and if not
     * throw an exception.
     */
    public function checkContext() {
        $tags =& $this->required_tags[$this->context];
        
        foreach($tags as $name => $used) {
            if(!$used)
                throw new XmlPrinterException("The tag [{$name}] is required ".
                                               "in the [{$this->context}] section.");
            
            // reset it
            $tags[$name] = FALSE;
        }
    }
    
    
    /**
     * Helper function to make sure URLs are nicely encoded for RSS.
     */
    static public function encodeURL($url = NULL) {
        
        // default to the current url
        if($url === NULL)
            $url = "http://". $_SERVER["HTTP_HOST"] . $_SERVER["REQUEST_URI"];
        
        // split up the url into its parts
        $url = parse_url($url);
        $get = array();
        parse_str(isset($url['query']) ? $url['query'] : '', $get);
                
        // encode each GET parameter
        foreach($get as $key => $val)
            $get[$key] = rawurlencode($val);
        
        // rebuild the query string
        $url['query'] = http_build_query($get);
        
        // rebuild the url
        $ret = '';
        $ret .= isset($url['scheme']) ? $url['scheme'] . '://' : '';
        $ret .= isset($url['user']) ? $url['user'] : '';
        $ret .= isset($url['pass']) ? ':'. $url['pass'] : '';
        $ret .= isset($url['user']) ? '@' : '';
        $ret .= isset($url['host']) ? $url['host'] : '';
        $ret .= isset($url['path']) ? $url['path'] : '';
        $ret .= isset($url['query']) ? $url['query'] : '';
        $ret .= isset($url['fragment']) ? '#'. $url['fragment'] : '';
        
        return $ret;
    }
}

/**
 * Function to create and output a basic RSS feed using a simple mapping
 * notation.
 * @author Peter Goodman
 */
function create_rss_feed(array $channel_map, $items, array $item_map = NULL) {
        
    // make sure we're getting something that we can loop over for $items
    if(!is_array($items) && !($items instanceof Traversable))
        throw new UnexpectedValueException("Function [create_rss_feed] expected ".
                                           "array or iterator for RSS items.");
    
    // the default item mapping, includes all RSS 2.0 standard optional item
    // tags
    if(empty($item_map)) {
        $tags = array('title','link','description','author','category',
                      'comments','enclosure','guid','pubDate','source');
        $item_map = array_combine($tags, $tags); // yes I'm lazy.
    }
    
    // bring in the feed builder/printer
    $rss = new RssFeedPrinter;
    $rss->open_channel();
    
    // deal with <channel> and the top level tags in it
    foreach($channel_map as $tag => $value)     
        __rss_tag($rss, $tag, $value);
    
    // deal with each <item> tag
    foreach($items as $item) {
        $rss->open_item();
        
        // deal with tags within <item> by mapping properties/indexes in each
        // $item to the tags that go within the <item>.
        foreach($item_map as $tag => $index) {
            
            $value = NULL;
            
            // is this an object and does it have the property we're looking
            // for?
            if(is_object($item) && property_exists($item, $index))
                $value = $item->$index;
            
            // is this an array, or does it have array access?
            if(NULL === $value && (is_array($item) || $item instanceof ArrayAccess))
                if(isset($item[$index]))
                    $value = $item[$index];
            
            // if the value is null it means that the mapping didn't find
            // anything to map to in the item, so don't include this tag in
            // the item
            if(NULL === $value)
                continue;
            
            // deal with the tag and any special cases that need to be applied
            // to its values
            __rss_tag($rss, $tag, $value);  
        }

        $rss->close_item();
    }
    $rss->close_channel();
    
    // output the feed
    $rss->doHeader();
    echo $rss;  
}

/**
 * Create the internal tags of an RSS feed while being aware of special cases.
 * @author Peter Goodman
 * @internal
 */
function __rss_tag(RssFeedPrinter &$rss, $tag, $value = '') {
    $rss->open($tag);
    
    // deal with special cases
    switch($tag) {
        
        // make sure the link query parameters are escaped properly
        case 'link':
            $rss->hasContent(RssFeedPrinter::encodeURL($value));
            break;
        
        // make sure we format the date correctly
        case 'pubDate':
            if(is_string($value))
                $value = strtotime($value);
            
            $rss->hasContent(date(RssFeedPrinter::DATE_FORMAT, 
                             (int)$value));
            break;
        
        // assumption: this is a permalink
        case 'guid':
            $rss->isPermaLink("true")
                ->hasContent(RssFeedPrinter::encodeURL($value));
            break;
            
        // everything else, flat assumption
        default:
            $rss->hasContent($value);
            break;
    }
    
    $rss->close();
}


Create a new paste based on this one


Comments: