I'm working on modernizing Rosetta Code's infrastructure. Starting with communications. Please accept this time-limited open invite to RC's Slack.. --Michael Mol (talk) 20:59, 30 May 2020 (UTC)

Process SMIL directives in XML data

From Rosetta Code
(Redirected from Loading animated 3D data)
Process SMIL directives in XML data is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

In order to represent evolutions of contents over time, the SMIL standard provides a solution to record the animation of data. Smil animations can be added to any kind of contents formated in XML.

The task is to create an utility that given the first Smiled XML file, would return the following ones:

<?xml version="1.0" ?>
<smil>
<X3D>
<Scene>
<Viewpoint position="0 0 8" orientation="0 0 1 0"/>
<PointLight color='1 1 1' location='0 2 0'/>
<Shape>
<Box size='2 1 2'>
<animate attributeName="size" from="2 1 2"
to="1 2 1" begin="0s" dur="10s"/>
</Box>
<Appearance>
<Material diffuseColor='0.0 0.6 1.0'>
<animate attributeName="diffuseColor" from="0.0 0.6 1.0"
to="1.0 0.4 0.0" begin="0s" dur="10s"/>
</Material>
</Appearance>
</Shape>
</Scene>
</X3D>
</smil>

At t = 0 second here is the expected output:

<?xml version="1.0" ?>
<X3D>
<Scene>
<Viewpoint position="0 0 8" orientation="0 0 1 0"/>
<PointLight color='1 1 1' location='0 2 0'/>
<Shape>
<Box size='2 1 2'/>
<Appearance>
<Material diffuseColor='0.0 0.6 1.0'/>
</Appearance>
</Shape>
</Scene>
</X3D>

At t = 2 second here is the expected output:

<?xml version="1.0" ?>
<X3D>
<Scene>
<Viewpoint position="0 0 8" orientation="0 0 1 0"/>
<PointLight color='1 1 1' location='0 2 0'/>
<Shape>
<Box size='1.8 1.2 1.8'/>
<Appearance>
<Material diffuseColor='0.2 0.56 0.8'/>
</Appearance>
</Shape>
</Scene>
</X3D>

Go[edit]

Library: etree
package main
 
import (
"fmt"
"github.com/beevik/etree"
"log"
"os"
"strconv"
"strings"
)
 
type animData struct {
element *etree.Element
attrib string
from string
to string
begin float64
dur float64
}
 
func check(err error) {
if err != nil {
log.Fatal(err)
}
}
 
func (ad *animData) AtTime(t float64) string {
beg := ad.begin
end := beg + ad.dur
if t < beg || t > end {
log.Fatalf("time must be in interval [%g, %g]", beg, end)
}
fromSplit := strings.Fields(ad.from)
toSplit := strings.Fields(ad.to)
le := len(fromSplit)
interSplit := make([]string, le)
for i := 0; i < le; i++ {
fromF, err := strconv.ParseFloat(fromSplit[i], 64)
check(err)
toF, err := strconv.ParseFloat(toSplit[i], 64)
check(err)
interF := (fromF*(end-t) + toF*(t-beg)) / ad.dur
interSplit[i] = fmt.Sprintf("%.2f", interF)
}
return strings.Join(interSplit, " ")
}
 
func main() {
doc := etree.NewDocument()
check(doc.ReadFromFile("smil.xml"))
smil := doc.SelectElement("smil")
if smil == nil {
log.Fatal("'smil' element not found")
}
x3d := smil.SelectElement("X3D")
if x3d == nil {
log.Fatal("'X3D' element not found")
}
doc.SetRoot(x3d) // remove 'smil' element
var ads []*animData
for _, a := range doc.FindElements("//animate") {
attrib := a.SelectAttrValue("attributeName", "?")
from := a.SelectAttrValue("from", "?")
to := a.SelectAttrValue("to", "?")
beginS := a.SelectAttrValue("begin", "?")
durS := a.SelectAttrValue("dur", "?")
if attrib == "?" || from == "?" || to == "?" ||
beginS == "?" || durS == "?" {
log.Fatal("an animate element has missing attribute(s)")
}
begin, err := strconv.ParseFloat(beginS[:len(beginS)-1], 64)
check(err)
dur, err := strconv.ParseFloat(durS[:len(durS)-1], 64)
check(err)
p := a.Parent()
if p == nil {
log.Fatal("an animate element has no parent")
}
pattrib := p.SelectAttrValue(attrib, "?")
if pattrib == "?" {
log.Fatal("an animate element's parent has missing attribute")
}
ads = append(ads, &animData{p, attrib, from, to, begin, dur})
p.RemoveChild(a) // remove 'animate' element
}
ts := []float64{0, 2}
for _, t := range ts {
for _, ad := range ads {
s := ad.AtTime(t)
ad.element.CreateAttr(ad.attrib, s)
}
doc.Indent(2)
fmt.Printf("At time = %g seconds:\n\n", t)
doc.WriteTo(os.Stdout)
fmt.Println()
}
}
Output:
At time = 0 seconds:

<?xml version="1.0" ?>
<X3D>
  <Scene>
    <Viewpoint position="0 0 8" orientation="0 0 1 0"/>
    <PointLight color="1 1 1" location="0 2 0"/>
    <Shape>
      <Box size="2.00 1.00 2.00"/>
      <Appearance>
        <Material diffuseColor="0.00 0.60 1.00"/>
      </Appearance>
    </Shape>
  </Scene>
</X3D>

At time = 2 seconds:

<?xml version="1.0" ?>
<X3D>
  <Scene>
    <Viewpoint position="0 0 8" orientation="0 0 1 0"/>
    <PointLight color="1 1 1" location="0 2 0"/>
    <Shape>
      <Box size="1.80 1.20 1.80"/>
      <Appearance>
        <Material diffuseColor="0.20 0.56 0.80"/>
      </Appearance>
    </Shape>
  </Scene>
</X3D>

Perl[edit]

Translation of: Raku
# 20201101 added Perl programming solution
 
use 5.014; # for s///r;
use strict;
use warnings;
 
BEGIN {
package Animatee;
use Moo;
has [qw(todo begin dur from to)] => ( is => 'rw');
$INC{"Animatee.pm"} = 1;
}
 
use Animatee;
use XML::Twig;
use List::AllUtils 'pairwise';
 
my $smil = <<'DATA';
<?xml version="1.0" ?> <smil><X3D><Scene><Viewpoint position="0 0 8" orientation="0 0 1 0" /><PointLight color="1 1 1" location="0 2 0" /><Shape><Box size="2 1 2"><animate attributeName="size" from="2 1 2" to="1 2 1" begin="0s" dur="10s" /></Box><Appearance><Material diffuseColor="0.0 0.6 1.0"><animate attributeName="diffuseColor" from="0.0 0.6 1.0" to="1.0 0.4 0.0" begin="0s" dur="10s" /></Material></Appearance></Shape></Scene></X3D></smil>
DATA

 
my %Parents;
 
my $x = XML::Twig->new->parse($smil);
 
for my $node ($x->findnodes("//animate")) {
 
my $y = $node->parent;
exists($Parents{$y}) ? (die) : ($Parents{my $k = $y->getName} = Animatee->new);
for my $animatee ($y->getChildNodes) {
my %h = %{$animatee->atts};
$Parents{$k}->todo($h{attributeName});
$Parents{$k}->from([ split(/\s+/,$h{from}) ]);
$Parents{$k}->to([ split(/\s+/,$h{to}) ]);
$Parents{$k}->begin( $h{begin} =~ m/\d+/g);
$Parents{$k}->dur ( $h{dur} =~ m/\d+/g);
}
}
 
my $z = XML::Twig->new->parse($smil =~ s/\<\/?smil\>//gr) or die;
 
for my $t ( 0, 2, 4 ) {
my $clone = $z;
while ( my( $k,$v ) = each %Parents) {
my @incre = pairwise { ($a-$b)/$v->dur } @{$v->to}, @{$v->from};
for my $f ($clone->findnodes("//$k")) {
my $c = join (' ', pairwise { $a+$b*$t } @{$v->from}, @incre);
$f->set_att($v->todo,$c);
}
for my $f ($clone->findnodes("//animate")) {
$f->delete
}
}
print "when t = $t\n";
print $clone->sprint,"\n";
}
Output:
when t = 0
<?xml version="1.0"?>
<X3D><Scene><Viewpoint orientation="0 0 1 0" position="0 0 8"/><PointLight color="1 1 1" location="0 2 0"/><Shape><Box size="2 1 2"></Box><Appearance><Material diffuseColor="0 0.6 1"></Material></Appearance></Shape></Scene></X3D>
when t = 2
<?xml version="1.0"?>
<X3D><Scene><Viewpoint orientation="0 0 1 0" position="0 0 8"/><PointLight color="1 1 1" location="0 2 0"/><Shape><Box size="1.8 1.2 1.8"></Box><Appearance><Material diffuseColor="0.2 0.56 0.8"></Material></Appearance></Shape></Scene></X3D>
when t = 4
<?xml version="1.0"?>
<X3D><Scene><Viewpoint orientation="0 0 1 0" position="0 0 8"/><PointLight color="1 1 1" location="0 2 0"/><Shape><Box size="1.6 1.4 1.6"></Box><Appearance><Material diffuseColor="0.4 0.52 0.6"></Material></Appearance></Shape></Scene></X3D>

Phix[edit]

include builtins\xml.e
 
constant xml = """
<?xml version="1.0" ?>
<smil>
<X3D>
<Scene>
<Viewpoint position="0 0 8" orientation="0 0 1 0"/>
<PointLight color='1 1 1' location='0 2 0'/>
<Shape>
<Box size='2 1 2'>
<animate attributeName="size" from="2 1 2"
to="1 2 1" begin="0s" dur="10s"/>
</Box>
<Appearance>
<Material diffuseColor='0.0 0.6 1.0'>
<animate attributeName="diffuseColor" from="0.0 0.6 1.0"
to="1.0 0.4 0.0" begin="0s" dur="10s"/>
</Material>
</Appearance>
</Shape>
</Scene>
</X3D>
</smil>
"""
 
function scan_all(sequence s, fmt)
for i=1 to length(s) do
{{s[i]}} = scanf(s[i],fmt)
end for
return s
end function
 
function animate_contents(sequence doc, atom t)
sequence a = xml_get_nodes(doc,"animate")
if a={} then
for i=1 to length(doc[XML_CONTENTS]) do
doc[XML_CONTENTS][i] = animate_contents(doc[XML_CONTENTS][i],t)
end for
else
for i=1 to length(doc[XML_CONTENTS]) do
if doc[XML_CONTENTS][i][XML_TAGNAME]="animate" then
string name = xml_get_attribute(doc[XML_CONTENTS][i],"attributeName"),
vfrm = xml_get_attribute(doc[XML_CONTENTS][i],"from"),
v_to = xml_get_attribute(doc[XML_CONTENTS][i],"to"),
sbeg = xml_get_attribute(doc[XML_CONTENTS][i],"begin"),
sdur = xml_get_attribute(doc[XML_CONTENTS][i],"dur")
sequence from = scan_all(split(vfrm),"%f"),
to_s = scan_all(split(v_to),"%f")
atom {{begin}} = scanf(sbeg,"%fs"),
{{durat}} = scanf(sdur,"%fs"),
fj = begin+durat-t,
tj = t-begin
-- plenty more error handling possible here...
if tj<0 or fj<0 or length(from)!=length(to_s) then ?9/0 end if
for j=1 to length(from) do
from[j] = sprintf("%.2f",(from[j]*fj+to_s[j]*tj)/durat)
end for
doc = xml_set_attribute(doc,name,join(from," "))
doc[XML_CONTENTS][i..i] = "" -- remove 'animate'
exit
end if
end for
end if
return doc
end function
 
function animate(sequence doc, atom t)
doc[XML_CONTENTS] = doc[XML_CONTENTS][XML_CONTENTS][1] -- remove smil
doc[XML_CONTENTS] = animate_contents(doc[XML_CONTENTS],t)
return doc
end function
 
sequence doc = xml_parse(xml)
if doc[XML_DOCUMENT]!="document"
or doc[XML_CONTENTS][XML_TAGNAME]!="smil"
or length(doc[XML_CONTENTS][XML_CONTENTS])!=1
or doc[XML_CONTENTS][XML_CONTENTS][1][XML_TAGNAME]!="X3D" then
 ?9/0
end if
printf(1,"At time = 0:\n\n")
puts(1,xml_sprint(animate(doc,0)))
printf(1,"\nAt time = 2:\n\n")
puts(1,xml_sprint(animate(doc,2)))
Output:
At time = 0:

<?xml version="1.0" ?>
<X3D>
  <Scene>
    <Viewpoint position="0 0 8" orientation="0 0 1 0" />
    <PointLight color="1 1 1" location="0 2 0" />
    <Shape>
      <Box size="2.00 1.00 2.00" />
      <Appearance>
        <Material diffuseColor="0.00 0.60 1.00" />
      </Appearance>
    </Shape>
  </Scene>
</X3D>

At time = 2:

<?xml version="1.0" ?>
<X3D>
  <Scene>
    <Viewpoint position="0 0 8" orientation="0 0 1 0" />
    <PointLight color="1 1 1" location="0 2 0" />
    <Shape>
      <Box size="1.80 1.20 1.80" />
      <Appearance>
        <Material diffuseColor="0.20 0.56 0.80" />
      </Appearance>
    </Shape>
  </Scene>
</X3D>

Raku[edit]

(formerly Perl 6) A crude attempt that only works with task data.

use XML::XPath;
 
my $smil = q:to<DATA>; # cramped verison, modified from task data
<?xml version="1.0" ?> <smil><X3D><Scene><Viewpoint position="0 0 8" orientation="0 0 1 0" /><PointLight color="1 1 1" location="0 2 0" /><Shape><Box size="2 1 2"><animate attributeName="size" from="2 1 2" to="1 2 1" begin="0s" dur="10s" /></Box><Appearance><Material diffuseColor="0.0 0.6 1.0"><animate attributeName="diffuseColor" from="0.0 0.6 1.0" to="1.0 0.4 0.0" begin="0s" dur="10s" /></Material></Appearance></Shape></Scene></X3D></smil>
DATA
 
class Animatee { has ($.todo, @.from, @.to, $.begin, $.dur) is rw };
 
my %Parents; # keys store the parent tags that got <animate> child
 
my $x = XML::XPath.new(xml => $smil) or die;
 
for @($x.find("//animate")) { # strangely need .List or @ coercion to work
my $y = .parent.name;
%Parents{$y}:exists ?? die() !! %Parents{$y} = Animatee.new; # unique only
for .parent.elements {
%Parents{$y}.todo = .attribs<attributeName>;
%Parents{$y}.from = .attribs<from>.split(/\s+/);
%Parents{$y}.to = .attribs<to>.split(/\s+/);
%Parents{$y}.begin = .attribs<begin>.match(/\d+/);
%Parents{$y}.dur = .attribs<dur>.match(/\d+/);
}
}
 
# use regex to strip SMIL tag and create a master template; sub-optimal approach
my $z = XML::XPath.new(xml => $smil.subst(/\<\/?smil\>/,'',:g:ii:ss)) or die;
 
for 0, 2, 4 -> $t { # task requires 0 & 2 only
my $clone = $z.clone; # work on a copy
for %Parents.kv -> $k,$v {
my @incre = ($v.to »-« $v.from) »/» $v.dur; # increment list
with $clone.find("//$k") { # moving attribute = @from + @increment*$t
.attribs{%Parents{$_.name}.todo} = $v.from »+« @incre »*» $t;
.removeChild($_); # ditch <animate> and friends
}
}
say "when t = ", $t;
say $clone.find("/");
}
Output:
when t = 0
<?xml version="1.0"?><X3D><Scene><Viewpoint position="0 0 8" orientation="0 0 1 0"/><PointLight location="0 2 0" color="1 1 1"/><Shape><Box size="2 1 2"/><Appearance><Material diffuseColor="0 0.6 1"/></Appearance></Shape></Scene></X3D>
when t = 2
<?xml version="1.0"?><X3D><Scene><Viewpoint position="0 0 8" orientation="0 0 1 0"/><PointLight location="0 2 0" color="1 1 1"/><Shape><Box size="1.8 1.2 1.8"/><Appearance><Material diffuseColor="0.2 0.56 0.8"/></Appearance></Shape></Scene></X3D>
when t = 4
<?xml version="1.0"?><X3D><Scene><Viewpoint position="0 0 8" orientation="0 0 1 0"/><PointLight location="0 2 0" color="1 1 1"/><Shape><Box size="1.6 1.4 1.6"/><Appearance><Material diffuseColor="0.4 0.52 0.6"/></Appearance></Shape></Scene></X3D>

Tcl[edit]

Works with: Tcl version 8.6
Library: tDOM
package require Tcl 8.6
package require tdom
 
# Applies a time-based interpolation to generate a space-separated list
proc interpolate {time info} {
dict with info {
scan $begin "%fs" begin
scan $dur "%fs" dur
}
if {$time < $begin} {
return $from
} elseif {$time > $begin+$dur} {
return $to
}
set delta [expr {($time - $begin) / $dur}]
return [lmap f $from t $to {expr {$f + ($t-$f)*$delta}}]
}
 
# Applies SMIL <transform> elements to their container
proc applySMILtransform {sourceDocument time} {
set doc [dom parse [$sourceDocument asXML]]
foreach smil [$doc selectNodes //smil] {
foreach context [$smil selectNodes {//*[animate]}] {
set animator [$context selectNodes animate]
set animated [$context selectNodes @[$animator @attributeName]]
$context removeChild $animator
$context setAttribute [$animator @attributeName] \
[interpolate $time [lindex [$animator asList] 1]]
}
if {[$smil parentNode] eq ""} {
set reparent 1
} else {
[$smil parentNode] replaceChild $smil [$smil firstChild]
}
}
if {[info exist reparent]} {
set doc [dom parse [[$smil firstChild] asXML]]
}
return $doc
}
 
set t [expr {[lindex $argv 0] + 0.0}]
set result [applySMILtransform [dom parse [read stdin]] $t]
puts {<?xml version="1.0" ?>}
puts -nonewline [$result asXML -indent 2]
Demonstration:

Note that input.smil contains the source document from the task description.

$ tclsh8.6 applySmil.tcl 0 < input.smil
<?xml version="1.0" ?>
<X3D>
  <Scene>
    <Viewpoint position="0 0 8" orientation="0 0 1 0"/>
    <PointLight color="1 1 1" location="0 2 0"/>
    <Shape>
      <Box size="2.0 1.0 2.0"/>
      <Appearance>
        <Material diffuseColor="0.0 0.6 1.0"/>
      </Appearance>
    </Shape>
  </Scene>
</X3D>
$ tclsh8.6 applySmil.tcl 2 < input.smil
<?xml version="1.0" ?>
<X3D>
  <Scene>
    <Viewpoint position="0 0 8" orientation="0 0 1 0"/>
    <PointLight color="1 1 1" location="0 2 0"/>
    <Shape>
      <Box size="1.8 1.2 1.8"/>
      <Appearance>
        <Material diffuseColor="0.2 0.5599999999999999 0.8"/>
      </Appearance>
    </Shape>
  </Scene>
</X3D>