[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: '''[https://sr.ht/~q3cpma/mangadex-tools/%|%mangadex-tools]''' and '''[https://sr.ht/~q3cpma/haggle/%|%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 ======