Version 1 of Atom feeds

Updated 2021-03-27 19:20:25 by q3cpma

q3cpma 2021-03-27:

Here's a little module to create and update Atom modules using tDOM I made to use in two of my project: mangadex-tools and haggle . Except a little ternary helper I left in, it is standalone.

# Simple Atom reading/writing, exported procs:
#          create, read, add_entry, write
package require tdom

# Ternary
proc ? {expr a {b ""}} {
        tailcall if $expr [list string cat $a] else [list string cat $b]
}

namespace eval atom {
        variable xmlns http://www.w3.org/2005/Atom

        proc timestamp {} {
                clock format [clock seconds] -format %Y-%m-%dT%XZ -timezone :UTC
        }

        # nodespec: {tag ?text? ?attrname attrval...?}
        # Use {tag {} attrname attrval...} if you want an attribute but no text
        proc node {doc nodespec} {
                variable xmlns

                set attrs [lassign $nodespec tag text]
                set node [$doc createElementNS $xmlns $tag]
                if {$text ne ""} {
                        $node appendChild [$doc createTextNode $text]
                }
                if {$attrs ne ""} {
                        $node setAttribute {*}$attrs
                }
                return $node
        }

        proc add {doc node nodespec} {
                $node appendChild [node $doc $nodespec]
        }

        proc root_add {doc nodespec} {
                add $doc [$doc documentElement] $nodespec
        }

        # Wrapper around domNode selectNodes
        proc select_nodes {doc args} {
                variable xmlns
                [$doc documentElement] selectNodes -namespaces [list atom $xmlns] \
                        {*}$args
        }

        # Ignore id for local feeds (a file:// URI will be used)
        proc create {path title {id {}}} {
                variable xmlns

                set path [file normalize $path]
                set atom [dict create path $path entry_count 0 modified 1]
                set doc [dom createDocumentNS $xmlns feed]
                dict set atom xml $doc
                root_add $doc [list title $title]
                root_add $doc [list id [? {$id ne ""} $id "file://$path"]]
                root_add $doc [list updated [timestamp]]
                return $atom
        }

        proc read {path} {
                set atom [dict create path [file normalize $path] modified 0]
                set chan [open $path]
                set doc [dom parse [read $chan]]
                close $chan
                dict set atom xml $doc
                dict set atom entry_count [llength [select_nodes $doc //atom:entry]]
                return $atom
        }

        proc read_or_create {path title} {
                if {[file exists $path]} {
                        read $path
                } else {
                        set ret [create $path $title]
                        atom write $ret
                        return $ret
                }
        }

        proc write {atom} {
                if {[dict get $atom modified]} {
                        set chan [open [dict get $atom path] w]
                        puts $chan [[dict get $atom xml] asXML -indent 2]
                        close $chan
                }
        }

        # Add an entry to the feed in the _atom variable
        # args is a dictionary containing the optional values for keys id, content
        # and link; no id means local feed, thus a unique URI based on feed path
        # and entry count will be used
        proc add_entry {_atom title args} {
                upvar $_atom atom

                set doc [dict get $atom xml]
                set root [$doc documentElement]
                set id [if {[dict exists $args id]} { \
                                        dict get $args id \
                                } else { \
                                        string cat "file://[dict get $atom path]#[dict get $atom entry_count]" \
                                }]
                set timestamp [timestamp]

                set entry [node $doc entry]
                add $doc $entry [list title $title]
                add $doc $entry [list id $id]
                add $doc $entry [list updated $timestamp]
                if {[dict exists $args content]} {
                        add $doc $entry [list content [dict exists $args content] type html]
                }
                if {[dict exists $args link]} {
                        add $doc $entry [list link {} href [dict exists $args link]]
                }

                $root appendChild $entry

                dict incr atom entry_count
                dict set atom modified 1
                [select_nodes $doc //atom:feed/atom:updated/text()] nodeValue $timestamp
        }

        namespace export create read read_or_create add_entry write
        namespace ensemble create
}

An example:

source atom.tcl

set feed [atom read_or_create atom.xml "My blog"]
atom add_entry feed "Article 1" \
        content "Content of article 1" \
        link "http://myblog.net/2021/03/article_1.html" \
        id   "http://myblog.net/2021/03/article_1.html"
atom write $feed