First batch of extra files

This commit is contained in:
2025-10-28 18:39:57 +01:00
parent d8c35077cb
commit 2bb22c7583
890 changed files with 40738 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
/*
* Create a view to simplify eps, host and tag access using the many to many
* tag tables. This view is a demonstration of what could be done in the live
* database, where more views could be created, of various levels of
* complexity, depending on need.
*/
CREATE OR REPLACE VIEW eht_view AS
SELECT
e.*,
h.host, h.email,
t.tag,
(SELECT group_concat(tag) FROM tags2 t2, eps_tags2_xref et2 WHERE
et2.tags2_id = t2.id GROUP BY et2.eps_id HAVING et2.eps_id = e.id)
AS taglist
FROM eps e, hosts h, eps_tags2_xref et, tags2 t
WHERE e.hostid = h.hostid
AND e.id = et.eps_id
AND et.tags2_id = t.id;
-- vim: syntax=sql:ts=8:ai:tw=78:et:fo=tcrqn21:comments+=b\:--

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,62 @@
/*
* -----------------------------------------------------------------------------
* Many-to-many tag tables
*
* The 'DROP ...' things are in this file in case we plan to regenerate
* everything, perhaps after a table design change.
* -----------------------------------------------------------------------------
*
* .............................................................................
*
* Create table 'tags2'
*
* This holds all tags with an associated index. It's called 'tags2' because
* we already have a 'tags' table demonstrating an alternative tag solution.
*
* .............................................................................
*
*/
-- DROP TABLE IF EXISTS tags2;
CREATE TABLE tags2 (
id int(5) PRIMARY KEY NOT NULL AUTO_INCREMENT,
tag varchar(200) NOT NULL,
lctag varchar(200) NOT NULL
);
/*
* An index to make it easier to find tags and to enforce uniqueness
*/
-- DROP INDEX tags2_tag ON tags2;
CREATE UNIQUE INDEX tags2_tag ON tags2 (tag);
/*
* .............................................................................
*
* Create table 'eps_tags2_xref'
*
* This is the cross reference or 'joining' table
*
* .............................................................................
*
*/
-- DROP TABLE IF EXISTS eps_tags2_xref;
CREATE TABLE eps_tags2_xref (
eps_id int(5) NOT NULL,
tags2_id int(5) NOT NULL
);
/*
* Make a primary key from the two columns
*/
-- DROP INDEX all_eps_tags2 ON eps_tags2_xref;
CREATE UNIQUE INDEX all_eps_tags2 ON eps_tags2_xref (eps_id,tags2_id);
/*
* Make a tag id index to speed deletion (special case)
*/
-- DROP INDEX all_tags ON eps_tags2_xref;
CREATE INDEX all_tags ON eps_tags2_xref (tags2_id);
-- vim: syntax=sql:ts=8:ai:tw=78:et:fo=tcrqn21:comments+=b\:--

View File

@@ -0,0 +1,769 @@
#!/usr/bin/env perl
#===============================================================================
#
# FILE: refresh_tags_2
#
# USAGE: ./refresh_tags_2
#
# DESCRIPTION: Parse tags from the eps.tags field and use them to populate
# the eps_tags2_xref and tags2 tables. The eps tag list is
# definitive (though it's quite limited since it's only 200
# characters long), and so the junction table eps_tags2_xref and
# the normalised tags table tags2 are kept in step by adding
# and deleting.
# This script is for demonstration purposes. It is not the
# definitive answer to the tag management problem in the HPR
# database, though it's close :-)
#
# OPTIONS: ---
# REQUIREMENTS: ---
# BUGS: ---
# NOTES: ---
# AUTHOR: Dave Morriss (djm), Dave.Morriss@gmail.com
# VERSION: 0.0.3
# CREATED: 2016-07-22 16:48:49
# REVISION: 2017-03-14 21:11:33
#
#===============================================================================
use 5.010;
use strict;
use warnings;
use utf8;
use Carp;
use Getopt::Long;
use Config::General;
use Text::CSV;
use SQL::Abstract;
use DBI;
use Data::Dumper;
#
# Version number (manually incremented)
#
our $VERSION = '0.0.3';
#
# Script and directory names
#
( my $PROG = $0 ) =~ s|.*/||mx;
( my $DIR = $0 ) =~ s|/?[^/]*$||mx;
$DIR = '.' unless $DIR;
#-------------------------------------------------------------------------------
# Declarations
#-------------------------------------------------------------------------------
#
# Constants and other declarations
#
my $basedir = "$ENV{HOME}/HPR/Database";
my $configfile = "$basedir/.hpr_db.cfg";
my ( $dbh, $sth1, $h1, $rv );
my ( %eps_tags, %tags_tags, %diffs );
#
# Enable Unicode mode
#
binmode STDOUT, ":encoding(UTF-8)";
binmode STDERR, ":encoding(UTF-8)";
#
# Load configuration data
#
my $conf = Config::General->new(
-ConfigFile => $configfile,
-InterPolateVars => 1,
-ExtendedAccess => 1,
);
my %config = $conf->getall();
#-------------------------------------------------------------------------------
# Options and arguments
#-------------------------------------------------------------------------------
#
# Process options
#
my %options;
Options( \%options );
Usage() if ( $options{'help'} );
#
# Collect options
#
my $verbose = ( defined( $options{verbose} ) ? $options{verbose} : 0 );
my $dry_run = ( defined( $options{'dry-run'} ) ? $options{'dry-run'} : 1 );
#-------------------------------------------------------------------------------
# Connect to the database
#-------------------------------------------------------------------------------
my $dbhost = $config{database}->{host} // '127.0.0.1';
my $dbport = $config{database}->{port} // 3306;
my $dbname = $config{database}->{name};
my $dbuser = $config{database}->{user};
my $dbpwd = $config{database}->{password};
$dbh = DBI->connect( "dbi:mysql:host=$dbhost;port=$dbport;database=$dbname",
$dbuser, $dbpwd, { AutoCommit => 1 } )
or croak $DBI::errstr;
#
# Enable client-side UTF8
#
$dbh->{mysql_enable_utf8} = 1;
#-------------------------------------------------------------------------------
# Collect and process the id numbers and tags from the 'eps' table
#-------------------------------------------------------------------------------
%eps_tags = %{ collect_eps_tags( $dbh, $verbose ) };
#-------------------------------------------------------------------------------
# Collect any tags we've already stashed in the database.
#-------------------------------------------------------------------------------
%tags_tags = %{ collect_db_tags( $dbh, $verbose ) };
#-------------------------------------------------------------------------------
# Now compare the two sources to look for differences
#-------------------------------------------------------------------------------
%diffs = %{ find_differences(\%eps_tags,\%tags_tags) };
#-------------------------------------------------------------------------------
# Perform the updates if there are any
#-------------------------------------------------------------------------------
if (%diffs) {
print "Differences found\n\n";
unless ($dry_run) {
#
# Scan for all deletions in the %diffs hash by traversing it by sorted
# episode number. If deletions are found for an episode they are
# performed.
#
foreach my $id ( sort { $a <=> $b } keys(%diffs) ) {
if ( exists( $diffs{$id}->{deletions} ) ) {
do_deletions( $dbh, $verbose, $id, $diffs{$id}->{deletions} );
}
}
#
# Prepare to search for tags
#
$sth1 = $dbh->prepare(q{SELECT * FROM tags2 WHERE tag = ?})
or die $DBI::errstr;
if ( $dbh->err ) {
warn $dbh->errstr;
}
#
# Scan for all additions in the %diffs hash
#
foreach my $id ( sort { $a <=> $b } keys(%diffs) ) {
if ( exists( $diffs{$id}->{additions} ) ) {
do_additions( $dbh, $sth1, $verbose, $id,
$diffs{$id}->{additions} );
}
}
#
# Having deleted all the requested rows from the junction table remove
# any tags that are "orphaned" as a consequence. If we were using
# foreign keys we could let the database do this.
#
$sth1 = $dbh->prepare(
q{DELETE FROM tags2
WHERE id NOT IN (SELECT DISTINCT tags2_id FROM eps_tags2_xref)}
) or die $DBI::errstr;
if ( $dbh->err ) {
warn $dbh->errstr;
}
$rv = $sth1->execute;
if ( $dbh->err ) {
warn $dbh->errstr;
}
$rv = 0 if ( $rv eq '0E0' );
#
# Report the action
#
if ($rv) {
print "Deleted ", $rv, " orphan tag", ( $rv != 1 ? 's' : '' ),
"\n";
}
}
else {
print "No changes made - dry run\n";
}
}
else {
print "No differences found\n";
}
exit;
#=== FUNCTION ================================================================
# NAME: collect_eps_tags
# PURPOSE: Collects the tags from the eps.tags field
# PARAMETERS: $dbh Database handle
# $verbose Verbosity level
# RETURNS: A reference to the hash created by collecting all the tags
# DESCRIPTION: FIXME
# THROWS: No exceptions
# COMMENTS: None
# SEE ALSO: N/A
#===============================================================================
sub collect_eps_tags {
my ( $dbh, $verbose ) = @_;
my ( $status, @fields, %hash );
my ( $sth, $h );
#
# For parsing the field as CSV
#
my $csv = Text::CSV_XS->new;
#
# Query the eps table for all the id and tags
#
$sth = $dbh->prepare(
q{SELECT id,tags FROM eps
WHERE length(tags) > 0
ORDER BY id}
) or die $DBI::errstr;
if ( $dbh->err ) {
warn $dbh->errstr;
}
$sth->execute;
if ( $dbh->err ) {
warn $dbh->errstr;
}
#
# Loop through what we got
#
while ( $h = $sth->fetchrow_hashref ) {
#
# Parse the tag list
#
$status = $csv->parse( $h->{tags} );
unless ($status) {
#
# Report any errors
#
print "Parse error on episode ", $h->{id}, "\n";
print $csv->error_input(), "\n";
next;
}
@fields = $csv->fields();
next unless (@fields);
#
# Trim all tags (don't alter $_ when doing it)
#
@fields = map {
my $t = $_;
$t =~ s/(^\s+|\s+$)//g;
$t;
} @fields;
#print "$h->{id}: ",join(",",@fields),"\n";
#
# Save the id and its tags, sorted for comparison
#
$hash{ $h->{id} } = [ sort @fields ];
}
#print Dumper(\%hash),"\n";
#
# Dump all id numbers and tags if the verbose level is high enough
#
if ( $verbose >= 3 ) {
print "\nTags collected from the 'eps' table\n\n";
foreach my $id ( sort { $a <=> $b } keys(%hash) ) {
printf "%04d: %s\n", $id, join( ",", @{ $hash{$id} } );
}
}
return \%hash;
}
#=== FUNCTION ================================================================
# NAME: collect_db_tags
# PURPOSE: Collects the tags already stored in the database
# PARAMETERS: $dbh Database handle
# $verbose Verbosity level
# RETURNS: A reference to the hash created by collecting all the tags
# DESCRIPTION:
# THROWS: No exceptions
# COMMENTS: None
# SEE ALSO: N/A
#===============================================================================
sub collect_db_tags {
my ( $dbh, $verbose ) = @_;
my %hash;
my ( $sth, $h );
#
# Query the database for tag data
#
# We use the junction table (eps_tags2_xref), traversing it by episode number
# and linking the table of tags (tags2). This results in a list of the tags
# relating to an episode, which should be similar to (if not the same as) the
# 'tags' field in the 'eps' table.
#
$sth = $dbh->prepare(
q{SELECT et.eps_id AS id,t.tag,t.lctag
FROM eps_tags2_xref et
JOIN tags2 t ON et.tags2_id = t.id
ORDER BY et.eps_id}
) or die $DBI::errstr;
if ( $dbh->err ) {
warn $dbh->errstr;
}
$sth->execute;
if ( $dbh->err ) {
warn $dbh->errstr;
}
#
# Loop through what we got, building an array of tags per episode number
#
while ( $h = $sth->fetchrow_hashref ) {
if ( defined( $hash{ $h->{id} } ) ) {
push( @{ $hash{ $h->{id} } }, $h->{tag} );
}
else {
$hash{ $h->{id} } = [ $h->{tag} ];
}
}
#
# Sort all the tag arrays for comparison
#
foreach my $id ( keys(%hash) ) {
$hash{$id} = [ sort @{ $hash{$id} } ];
}
#
# Dump all id numbers and tags if the verbose level is high enough
#
if ( $verbose >= 3 ) {
print "\nTags collected from the 'tags2' table\n\n";
foreach my $id ( sort { $a <=> $b } keys(%hash) ) {
printf "%04d: %s\n", $id, join( ",", @{ $hash{$id} } );
}
print '=-' x 40,"\n";
}
return \%hash;
}
#=== FUNCTION ================================================================
# NAME: find_differences
# PURPOSE: Find the differences between two hashes containing tags
# PARAMETERS: $master Reference to the master hash
# $slave Reference to the slave hash
# RETURNS: A reference to the hash created checking for differences
# DESCRIPTION: The function is presented with two hashes. The 'master' hash
# has come from the CSV string in the 'eps' table. The 'slave'
# hash has come from the table of tags 'tags2'. These hashes are
# keyed by episode number and each element contains a reference
# to a sorted array of tags.
# This function compares two tag arrays for an episode using
# function 'array_compare' and receives back a hash of additions
# and deletions:
# {
# additions => [ tag1, tag2 .. tagn ],
# deletions => [ tag1, tag2 .. tagn ],
# }
# These are stored in a result hash keyed by episode number, and
# a reference to this hash is returned to the caller.
# This function can report a lot of details about what has been
# found if the level of verbosity is high enough.
# THROWS: No exceptions
# COMMENTS: None
# SEE ALSO: N/A
#===============================================================================
sub find_differences {
my ($master,$slave) = @_;
my %hash;
foreach my $id ( sort { $a <=> $b } keys(%$master) ) {
my %iddiffs = array_compare( $master->{$id}, $slave->{$id} );
if (%iddiffs) {
if ( $verbose >= 1 ) {
#
# Report what was found if asked to
#
print "Episode: $id\n";
print "Update:\n\teps: ", join( ",", @{ $master->{$id} } ), "\n";
print "\ttags: ",
(
defined( $slave->{$id} )
? join( ",", @{ $slave->{$id} } )
: '--None--' ), "\n";
print '-' x 80,"\n";
}
$hash{$id} = {%iddiffs};
}
}
#
# Report differences and actions if the verbose level is high enough
#
if ( $verbose >= 2 ) {
print "\nDifferences and actions\n\n";
foreach my $id ( sort { $a <=> $b } keys(%hash) ) {
print "Episode: $id\n";
if ( exists( $hash{$id}->{deletions} ) ) {
print "Deletions: ";
print join( ",", @{ $hash{$id}->{deletions} } ), "\n";
}
if ( exists( $hash{$id}->{additions} ) ) {
print "Additions: ";
print join( ",", @{ $hash{$id}->{additions} } ), "\n";
}
print '-' x 80, "\n";
}
}
return \%hash;
}
#=== FUNCTION ================================================================
# NAME: do_deletions
# PURPOSE: Perform any deletions indicated in an array for a given
# episode
# PARAMETERS: $dbh Database handle
# $verbose Verbosity level
# $id Episode number
# $tags Reference to an array of tags for this episode
# RETURNS: Nothing
# DESCRIPTION: A tag deletion consists of its removal from the joining table.
# Only when there are no more references to the actual tag can
# it then be deleted. If the tables were in a database with
# foreign keys then we could leave the database itself to handle
# this (MariaDB could do it but we'd need to redefine the tables
# to use InnoDB rather than MyISAM. The latter is the legacy
# table structure from the days when MySQL didn't have foreign
# keys).
# This function does not perform the tag deletion since this
# easier to leave until all deletions have finished.
# THROWS: No exceptions
# COMMENTS: None
# SEE ALSO: N/A
#===============================================================================
sub do_deletions {
my ( $dbh, $verbose, $id, $tags ) = @_;
my ( $stmt, @bind, %data, %where );
#
# We will dynamically build SQL as we go
#
my $sql = SQL::Abstract->new;
#
# Process the list of tags we have been given
#
for my $i ( 0 .. $#$tags ) {
#
# Set up a deletion '... where eps_id = ? and
# tags2 = (select id from tags2 where tag = ?)'
#
my ( $sub_stmt, @sub_bind )
= ( "SELECT id FROM tags2 WHERE tag = ?", $tags->[$i] );
%where = (
eps_id => $id,
tags2_id => \[ "= ($sub_stmt)" => @sub_bind ]
);
( $stmt, @bind ) = $sql->delete( 'eps_tags2_xref', \%where );
if ( $verbose >= 2 ) {
print "Statement: $stmt\n";
print "Bind: ", join( ",", @bind ), "\n";
}
#
# Do the deletion
#
my $sth = $dbh->prepare($stmt);
my $rv = $sth->execute(@bind);
if ( $dbh->err ) {
warn $dbh->errstr;
}
$rv = 0 if ( $rv eq '0E0' );
#
# Report the action
#
if ($rv) {
print "Deleted tag for show $id ($tags->[$i])\n";
}
}
print "Deleted ", scalar(@$tags), " row",
( scalar(@$tags) != 1 ? 's' : '' ), "\n";
}
#=== FUNCTION ================================================================
# NAME: do_additions
# PURPOSE: Perform any additions indicated in an array for a given
# episode
# PARAMETERS: $dbh Database handle
# $sth A prepared database handle with a query to
# search for the target tag
# $verbose Verbosity level
# $id Episode number
# $tags Reference to an array of tags for this episode
# RETURNS: Nothing
# DESCRIPTION: The addition of a tag for an episode consists of creating the
# tag in the 'tags2' table (unless it already exists) and
# making a joining table entry for it. This what this function
# does.
# FIXME: Not very resilient to failure.
# THROWS: No exceptions
# COMMENTS: None
# SEE ALSO: N/A
#===============================================================================
sub do_additions {
my ( $dbh, $sth, $verbose, $id, $tags ) = @_;
my ( $sth1, $rv, $h, $tid, $stmt, @bind, %data );
#
# We will dynamically build SQL as we go
#
my $sql = SQL::Abstract->new;
my @lctags = map { lc($_) } @$tags;
#
# Loop through the array of tags (using an integer so we can index the
# current tag)
#
for my $i ( 0 .. $#$tags ) {
#
# Look to see if this tag exists
#
$sth->execute( $tags->[$i] );
if ( $dbh->err ) {
warn $dbh->errstr;
}
#
# If it's already in the table just store the id otherwise
# add a new entry
#
if ( $h = $sth->fetchrow_hashref ) {
$tid = $h->{id};
}
else {
#
# Build the row we're going to add
#
%data = (
tag => $tags->[$i],
lctag => $lctags[$i]
);
#
# Build the SQL, reporting the result if asked
#
( $stmt, @bind ) = $sql->insert( 'tags2', \%data );
if ( $verbose >= 2 ) {
print "Statement: $stmt\n";
print "Bind: ", join( ",", @bind ), "\n";
}
#
# Add the tag to 'tags2'
#
$sth1 = $dbh->prepare($stmt);
$rv = $sth1->execute(@bind);
if ( $dbh->err ) {
warn $dbh->errstr;
}
$rv = 0 if ( $rv eq '0E0' );
#
# Ask the database foir the id we just added
# FIXME: what if it failed?
#
$tid = $sth1->{mysql_insertid};
#
# Report the action
#
if ($rv) {
print "Added new tag '$tags->[$i]' ($tid)\n";
}
}
#
# Now we know we have a tag in the tags2 table so now we can create
# the eps_tags2_xref entry
#
%data = (
eps_id => $id,
tags2_id => $tid
);
#
# Build the SQL, reporting the result if asked
#
( $stmt, @bind ) = $sql->insert( 'eps_tags2_xref', \%data );
if ( $verbose >= 2 ) {
print "Statement: $stmt\n";
print "Bind: ", join( ",", @bind ), "\n";
}
#
# Add the row
#
$sth1 = $dbh->prepare($stmt);
$rv = $sth1->execute(@bind);
if ( $dbh->err ) {
warn $dbh->errstr;
}
$rv = 0 if ( $rv eq '0E0' );
#
# Report the action
#
if ($rv) {
printf "Added new junction row (eps_id=%s,tags2_id=%s -> %s)\n",
$id, $tid, $tags->[$i];
}
}
print "Added ", scalar(@$tags), " row",
( scalar(@$tags) != 1 ? 's' : '' ), "\n";
}
#=== FUNCTION ================================================================
# NAME: array_compare
# PURPOSE: Compares the elements of two arrays to see if an element
# present in the master is also present in the slave
# PARAMETERS: $arr1 A reference to the first array; the MASTER
# $arr2 A reference to the second array; the SLAVE
# RETURNS: A hash containing arrays of additions and deletions of the
# elements that are different. The structure is:
# {
# additions => [ tag1, tag2 .. tagn ],
# deletions => [ tag1, tag2 .. tagn ],
# }
# The returned hash will be empty if there are no differences.
# DESCRIPTION: The requirement is to find if there are differences, then to
# find what they are so that other code can make the slave array
# match the master. The two arrays come from a database, so
# we're trying to make a second source (slave) equal the first
# (master).
# THROWS: No exceptions
# COMMENTS: None
# SEE ALSO: N/A
#===============================================================================
sub array_compare {
my ( $arr1, $arr2 ) = @_;
my %res;
my ( @additions, @deletions );
#
# Use hashes to make it easier to find existence of stuff
#
my %h1 = map { lc($_) => 1 } @$arr1;
my %h2 = map { lc($_) => 1 } @$arr2;
#
# Find additions
#
for my $key ( keys(%h1) ) {
unless ( exists( $h2{$key} ) ) {
push( @additions, $key );
}
}
#
# Find deletions
#
for my $key ( keys(%h2) ) {
unless ( exists( $h1{$key} ) ) {
push( @deletions, $key );
}
}
$res{additions} = [@additions] if @additions;
$res{deletions} = [@deletions] if @deletions;
return %res;
}
#=== FUNCTION ================================================================
# NAME: Usage
# PURPOSE: Display a usage message and exit
# PARAMETERS: None
# RETURNS: To command line level with exit value 1
# DESCRIPTION: Builds the usage message using global values
# THROWS: no exceptions
# COMMENTS: none
# SEE ALSO: n/a
#===============================================================================
sub Usage {
print STDERR <<EOD;
Usage: $PROG [options] project
$PROG v$VERSION
-help Display this information
-[no]dry-run Display what would have been done but make no changes.
Default is -dry-run.
-verbose A repeatable option which turns up the verbosity from
0 (silent) to 3 (lots and lots of stuff). Default is 0.
EOD
exit(1);
}
#=== FUNCTION ================================================================
# NAME: Options
# PURPOSE: Processes command-line options
# PARAMETERS: $optref Hash reference to hold the options
# RETURNS: Undef
# DESCRIPTION:
# THROWS: no exceptions
# COMMENTS: none
# SEE ALSO: n/a
#===============================================================================
sub Options {
my ($optref) = @_;
my @options = ( "help", "verbose+", "dry-run!", );
if ( !GetOptions( $optref, @options ) ) {
Usage();
}
return;
}
# vim: syntax=perl:ts=8:sw=4:et:ai:tw=78:fo=tcrqn21:fdm=marker

View File

@@ -0,0 +1,207 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.2" width="183.01mm" height="115.01mm" viewBox="1350 1450 18301 11501" preserveAspectRatio="xMidYMid" fill-rule="evenodd" stroke-width="28.222" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" xmlns:ooo="http://xml.openoffice.org/svg/export" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:presentation="http://sun.com/xmlns/staroffice/presentation" xmlns:smil="http://www.w3.org/2001/SMIL20/" xmlns:anim="urn:oasis:names:tc:opendocument:xmlns:animation:1.0" xml:space="preserve">
<defs class="ClipPathGroup">
<clipPath id="presentation_clip_path" clipPathUnits="userSpaceOnUse">
<rect x="1350" y="1450" width="18301" height="11501"/>
</clipPath>
<clipPath id="presentation_clip_path_shrink" clipPathUnits="userSpaceOnUse">
<rect x="1368" y="1461" width="18265" height="11478"/>
</clipPath>
</defs>
<defs>
<font id="EmbeddedFont_1" horiz-adv-x="2048">
<font-face font-family="DejaVu Sans embedded" units-per-em="2048" font-weight="normal" font-style="normal" ascent="1879" descent="476"/>
<missing-glyph horiz-adv-x="2048" d="M 0,0 L 2047,0 2047,2047 0,2047 0,0 Z"/>
<glyph unicode="t" horiz-adv-x="689" d="M 375,1438 L 375,1120 754,1120 754,977 375,977 375,369 C 375,278 388,219 413,193 438,167 488,154 565,154 L 754,154 754,0 565,0 C 423,0 325,27 271,80 217,133 190,229 190,369 L 190,977 55,977 55,1120 190,1120 190,1438 375,1438 Z"/>
<glyph unicode="s" horiz-adv-x="848" d="M 907,1087 L 907,913 C 855,940 801,960 745,973 689,986 631,993 571,993 480,993 411,979 366,951 320,923 297,881 297,825 297,782 313,749 346,725 379,700 444,677 543,655 L 606,641 C 737,613 830,574 885,523 940,472 967,400 967,309 967,205 926,123 844,62 761,1 648,-29 504,-29 444,-29 382,-23 317,-12 252,0 183,18 111,41 L 111,231 C 179,196 246,169 312,152 378,134 443,125 508,125 595,125 661,140 708,170 755,199 778,241 778,295 778,345 761,383 728,410 694,437 620,462 506,487 L 442,502 C 328,526 246,563 195,613 144,662 119,730 119,817 119,922 156,1004 231,1061 306,1118 412,1147 549,1147 617,1147 681,1142 741,1132 801,1122 856,1107 907,1087 Z"/>
<glyph unicode="p" horiz-adv-x="1007" d="M 371,168 L 371,-426 186,-426 186,1120 371,1120 371,950 C 410,1017 459,1066 518,1099 577,1131 647,1147 729,1147 865,1147 976,1093 1061,985 1146,877 1188,735 1188,559 1188,383 1146,241 1061,133 976,25 865,-29 729,-29 647,-29 577,-13 518,20 459,52 410,101 371,168 Z M 997,559 C 997,694 969,801 914,878 858,955 781,993 684,993 587,993 510,955 455,878 399,801 371,694 371,559 371,424 399,318 455,241 510,164 587,125 684,125 781,125 858,164 914,241 969,318 997,424 997,559 Z"/>
<glyph unicode="o" horiz-adv-x="1033" d="M 627,991 C 528,991 450,953 393,876 336,799 307,693 307,559 307,425 336,320 393,243 450,166 528,127 627,127 725,127 803,166 860,243 917,320 946,426 946,559 946,692 917,797 860,875 803,952 725,991 627,991 Z M 627,1147 C 787,1147 913,1095 1004,991 1095,887 1141,743 1141,559 1141,376 1095,232 1004,128 913,23 787,-29 627,-29 466,-29 341,23 250,128 159,232 113,376 113,559 113,743 159,887 250,991 341,1095 466,1147 627,1147 Z"/>
<glyph unicode="n" horiz-adv-x="927" d="M 1124,676 L 1124,0 940,0 940,670 C 940,776 919,855 878,908 837,961 775,987 692,987 593,987 514,955 457,892 400,829 371,742 371,633 L 371,0 186,0 186,1120 371,1120 371,946 C 415,1013 467,1064 527,1097 586,1130 655,1147 733,1147 862,1147 959,1107 1025,1028 1091,948 1124,831 1124,676 Z"/>
<glyph unicode="l" horiz-adv-x="186" d="M 193,1556 L 377,1556 377,0 193,0 193,1556 Z"/>
<glyph unicode="j" horiz-adv-x="424" d="M 193,1120 L 377,1120 377,-20 C 377,-163 350,-266 296,-330 241,-394 154,-426 33,-426 L -37,-426 -37,-270 12,-270 C 82,-270 130,-254 155,-222 180,-189 193,-122 193,-20 L 193,1120 Z M 193,1556 L 377,1556 377,1323 193,1323 193,1556 Z"/>
<glyph unicode="i" horiz-adv-x="186" d="M 193,1120 L 377,1120 377,0 193,0 193,1120 Z M 193,1556 L 377,1556 377,1323 193,1323 193,1556 Z"/>
<glyph unicode="g" horiz-adv-x="1006" d="M 930,573 C 930,706 903,810 848,883 793,956 715,993 616,993 517,993 441,956 386,883 331,810 303,706 303,573 303,440 331,337 386,264 441,191 517,154 616,154 715,154 793,191 848,264 903,337 930,440 930,573 Z M 1114,139 C 1114,-52 1072,-193 987,-287 902,-379 773,-426 598,-426 533,-426 472,-421 415,-412 358,-402 302,-387 248,-367 L 248,-188 C 302,-217 355,-239 408,-253 461,-267 514,-274 569,-274 690,-274 780,-242 840,-180 900,-116 930,-21 930,106 L 930,197 C 892,131 843,82 784,49 725,16 654,0 571,0 434,0 323,52 239,157 155,262 113,400 113,573 113,746 155,885 239,990 323,1095 434,1147 571,1147 654,1147 725,1131 784,1098 843,1065 892,1016 930,950 L 930,1120 1114,1120 1114,139 Z"/>
<glyph unicode="e" horiz-adv-x="1033" d="M 1151,606 L 1151,516 305,516 C 313,389 351,293 420,227 488,160 583,127 705,127 776,127 844,136 911,153 977,170 1043,196 1108,231 L 1108,57 C 1042,29 974,8 905,-7 836,-22 765,-29 694,-29 515,-29 374,23 270,127 165,231 113,372 113,549 113,732 163,878 262,986 361,1093 494,1147 662,1147 813,1147 932,1099 1020,1002 1107,905 1151,773 1151,606 Z M 967,659 C 966,760 938,841 883,901 828,961 755,991 664,991 561,991 479,962 418,904 356,846 320,764 311,659 L 967,659 Z"/>
<glyph unicode="d" horiz-adv-x="1006" d="M 930,950 L 930,1556 1114,1556 1114,0 930,0 930,168 C 891,101 843,52 784,20 725,-13 654,-29 571,-29 436,-29 326,25 241,133 156,241 113,383 113,559 113,735 156,877 241,985 326,1093 436,1147 571,1147 654,1147 725,1131 784,1099 843,1066 891,1017 930,950 Z M 303,559 C 303,424 331,318 387,241 442,164 519,125 616,125 713,125 790,164 846,241 902,318 930,424 930,559 930,694 902,801 846,878 790,955 713,993 616,993 519,993 442,955 387,878 331,801 303,694 303,559 Z"/>
<glyph unicode="b" horiz-adv-x="1007" d="M 997,559 C 997,694 969,801 914,878 858,955 781,993 684,993 587,993 510,955 455,878 399,801 371,694 371,559 371,424 399,318 455,241 510,164 587,125 684,125 781,125 858,164 914,241 969,318 997,424 997,559 Z M 371,950 C 410,1017 459,1066 518,1099 577,1131 647,1147 729,1147 865,1147 976,1093 1061,985 1146,877 1188,735 1188,559 1188,383 1146,241 1061,133 976,25 865,-29 729,-29 647,-29 577,-13 518,20 459,52 410,101 371,168 L 371,0 186,0 186,1556 371,1556 371,950 Z"/>
<glyph unicode="a" horiz-adv-x="953" d="M 702,563 C 553,563 450,546 393,512 336,478 307,420 307,338 307,273 329,221 372,183 415,144 473,125 547,125 649,125 731,161 793,234 854,306 885,402 885,522 L 885,563 702,563 Z M 1069,639 L 1069,0 885,0 885,170 C 843,102 791,52 728,20 665,-13 589,-29 498,-29 383,-29 292,3 225,68 157,132 123,218 123,326 123,452 165,547 250,611 334,675 460,707 627,707 L 885,707 885,725 C 885,810 857,875 802,922 746,968 668,991 567,991 503,991 441,983 380,968 319,953 261,930 205,899 L 205,1069 C 272,1095 338,1115 401,1128 464,1141 526,1147 586,1147 748,1147 869,1105 949,1021 1029,937 1069,810 1069,639 Z"/>
<glyph unicode="6" horiz-adv-x="1033" d="M 676,827 C 585,827 514,796 461,734 408,672 381,587 381,479 381,372 408,287 461,225 514,162 585,131 676,131 767,131 839,162 892,225 945,287 971,372 971,479 971,587 945,672 892,734 839,796 767,827 676,827 Z M 1077,1460 L 1077,1276 C 1026,1300 975,1318 924,1331 872,1344 821,1350 770,1350 637,1350 535,1305 465,1215 394,1125 354,989 344,807 383,865 433,910 492,941 551,972 617,987 688,987 838,987 957,942 1044,851 1131,760 1174,636 1174,479 1174,326 1129,203 1038,110 947,17 827,-29 676,-29 503,-29 371,37 280,170 189,302 143,494 143,745 143,981 199,1169 311,1310 423,1450 573,1520 762,1520 813,1520 864,1515 916,1505 967,1495 1021,1480 1077,1460 Z"/>
<glyph unicode="5" horiz-adv-x="953" d="M 221,1493 L 1014,1493 1014,1323 406,1323 406,957 C 435,967 465,975 494,980 523,985 553,987 582,987 749,987 881,941 978,850 1075,759 1124,635 1124,479 1124,318 1074,194 974,105 874,16 733,-29 551,-29 488,-29 425,-24 360,-13 295,-2 227,14 158,35 L 158,238 C 218,205 280,181 344,165 408,149 476,141 547,141 662,141 754,171 821,232 888,293 922,375 922,479 922,583 888,665 821,726 754,787 662,817 547,817 493,817 439,811 386,799 332,787 277,768 221,743 L 221,1493 Z"/>
<glyph unicode="4" horiz-adv-x="1086" d="M 774,1317 L 264,520 774,520 774,1317 Z M 721,1493 L 975,1493 975,520 1188,520 1188,352 975,352 975,0 774,0 774,352 100,352 100,547 721,1493 Z"/>
<glyph unicode="3" horiz-adv-x="980" d="M 831,805 C 928,784 1003,741 1058,676 1112,611 1139,530 1139,434 1139,287 1088,173 987,92 886,11 742,-29 555,-29 492,-29 428,-23 362,-11 295,2 227,20 156,45 L 156,240 C 212,207 273,183 340,166 407,149 476,141 549,141 676,141 772,166 839,216 905,266 938,339 938,434 938,522 907,591 846,641 784,690 698,715 588,715 L 414,715 414,881 596,881 C 695,881 771,901 824,941 877,980 903,1037 903,1112 903,1189 876,1248 822,1289 767,1330 689,1350 588,1350 533,1350 473,1344 410,1332 347,1320 277,1301 201,1276 L 201,1456 C 278,1477 350,1493 417,1504 484,1515 547,1520 606,1520 759,1520 881,1485 970,1416 1059,1346 1104,1252 1104,1133 1104,1050 1080,981 1033,924 986,867 918,827 831,805 Z"/>
<glyph unicode="2" horiz-adv-x="953" d="M 393,170 L 1098,170 1098,0 150,0 150,170 C 227,249 331,356 464,490 596,623 679,709 713,748 778,821 823,882 849,933 874,983 887,1032 887,1081 887,1160 859,1225 804,1275 748,1325 675,1350 586,1350 523,1350 456,1339 386,1317 315,1295 240,1262 160,1217 L 160,1421 C 241,1454 317,1478 388,1495 459,1512 523,1520 582,1520 737,1520 860,1481 952,1404 1044,1327 1090,1223 1090,1094 1090,1033 1079,975 1056,920 1033,865 991,800 930,725 913,706 860,650 771,558 682,465 556,336 393,170 Z"/>
<glyph unicode="1" horiz-adv-x="900" d="M 254,170 L 584,170 584,1309 225,1237 225,1421 582,1493 784,1493 784,170 1114,170 1114,0 254,0 254,170 Z"/>
<glyph unicode=" " horiz-adv-x="635"/>
</font>
</defs>
<defs class="TextShapeIndex">
<g ooo:slide="id1" ooo:id-list="id3 id4 id5 id6 id7 id8 id9 id10 id11 id12 id13 id14 id15 id16 id17 id18 id19"/>
</defs>
<defs class="EmbeddedBulletChars">
<g id="bullet-char-template(57356)" transform="scale(0.00048828125,-0.00048828125)">
<path d="M 580,1141 L 1163,571 580,0 -4,571 580,1141 Z"/>
</g>
<g id="bullet-char-template(57354)" transform="scale(0.00048828125,-0.00048828125)">
<path d="M 8,1128 L 1137,1128 1137,0 8,0 8,1128 Z"/>
</g>
<g id="bullet-char-template(10146)" transform="scale(0.00048828125,-0.00048828125)">
<path d="M 174,0 L 602,739 174,1481 1456,739 174,0 Z M 1358,739 L 309,1346 659,739 1358,739 Z"/>
</g>
<g id="bullet-char-template(10132)" transform="scale(0.00048828125,-0.00048828125)">
<path d="M 2015,739 L 1276,0 717,0 1260,543 174,543 174,936 1260,936 717,1481 1274,1481 2015,739 Z"/>
</g>
<g id="bullet-char-template(10007)" transform="scale(0.00048828125,-0.00048828125)">
<path d="M 0,-2 C -7,14 -16,27 -25,37 L 356,567 C 262,823 215,952 215,954 215,979 228,992 255,992 264,992 276,990 289,987 310,991 331,999 354,1012 L 381,999 492,748 772,1049 836,1024 860,1049 C 881,1039 901,1025 922,1006 886,937 835,863 770,784 769,783 710,716 594,584 L 774,223 C 774,196 753,168 711,139 L 727,119 C 717,90 699,76 672,76 641,76 570,178 457,381 L 164,-76 C 142,-110 111,-127 72,-127 30,-127 9,-110 8,-76 1,-67 -2,-52 -2,-32 -2,-23 -1,-13 0,-2 Z"/>
</g>
<g id="bullet-char-template(10004)" transform="scale(0.00048828125,-0.00048828125)">
<path d="M 285,-33 C 182,-33 111,30 74,156 52,228 41,333 41,471 41,549 55,616 82,672 116,743 169,778 240,778 293,778 328,747 346,684 L 369,508 C 377,444 397,411 428,410 L 1163,1116 C 1174,1127 1196,1133 1229,1133 1271,1133 1292,1118 1292,1087 L 1292,965 C 1292,929 1282,901 1262,881 L 442,47 C 390,-6 338,-33 285,-33 Z"/>
</g>
<g id="bullet-char-template(9679)" transform="scale(0.00048828125,-0.00048828125)">
<path d="M 813,0 C 632,0 489,54 383,161 276,268 223,411 223,592 223,773 276,916 383,1023 489,1130 632,1184 813,1184 992,1184 1136,1130 1245,1023 1353,916 1407,772 1407,592 1407,412 1353,268 1245,161 1136,54 992,0 813,0 Z"/>
</g>
<g id="bullet-char-template(8226)" transform="scale(0.00048828125,-0.00048828125)">
<path d="M 346,457 C 273,457 209,483 155,535 101,586 74,649 74,723 74,796 101,859 155,911 209,963 273,989 346,989 419,989 480,963 531,910 582,859 608,796 608,723 608,648 583,586 532,535 482,483 420,457 346,457 Z"/>
</g>
<g id="bullet-char-template(8211)" transform="scale(0.00048828125,-0.00048828125)">
<path d="M -4,459 L 1135,459 1135,606 -4,606 -4,459 Z"/>
</g>
<g id="bullet-char-template(61548)" transform="scale(0.00048828125,-0.00048828125)">
<path d="M 173,740 C 173,903 231,1043 346,1159 462,1274 601,1332 765,1332 928,1332 1067,1274 1183,1159 1299,1043 1357,903 1357,740 1357,577 1299,437 1183,322 1067,206 928,148 765,148 601,148 462,206 346,322 231,437 173,577 173,740 Z"/>
</g>
</defs>
<defs class="TextEmbeddedBitmaps"/>
<g class="SlideGroup">
<g>
<g id="container-id1">
<g id="id1" class="Slide" clip-path="url(#presentation_clip_path)">
<g class="Page">
<g class="Group">
<g class="Group">
<g class="com.sun.star.drawing.CustomShape">
<g id="id3">
<rect class="BoundingBox" stroke="none" fill="none" x="1949" y="2549" width="4753" height="6103"/>
<path fill="none" stroke="rgb(0,0,0)" d="M 4325,8650 L 1950,8650 1950,2550 6700,2550 6700,8650 4325,8650 Z"/>
</g>
</g>
<g class="com.sun.star.drawing.LineShape">
<g id="id4">
<rect class="BoundingBox" stroke="none" fill="none" x="1949" y="3499" width="4753" height="3"/>
<path fill="none" stroke="rgb(0,0,0)" d="M 1950,3500 L 6700,3500"/>
</g>
</g>
<g class="com.sun.star.drawing.TextShape">
<g id="id5">
<rect class="BoundingBox" stroke="none" fill="none" x="2806" y="2700" width="3064" height="663"/>
<text class="TextShape"><tspan class="TextParagraph" font-family="DejaVu Sans, sans-serif" font-size="353px" font-weight="400"><tspan class="TextPosition" x="3056" y="3151"><tspan fill="rgb(0,0,0)" stroke="none">episodes table</tspan></tspan></tspan></text>
</g>
</g>
</g>
<g class="com.sun.star.drawing.TextShape">
<g id="id6">
<rect class="BoundingBox" stroke="none" fill="none" x="3421" y="5237" width="1881" height="665"/>
<path fill="none" stroke="rgb(0,0,0)" d="M 4361,5900 L 3422,5900 3422,5238 5300,5238 5300,5900 4361,5900 Z"/>
<text class="TextShape"><tspan class="TextParagraph" font-family="DejaVu Sans, sans-serif" font-size="353px" font-weight="400"><tspan class="TextPosition" x="3672" y="5689"><tspan fill="rgb(0,0,0)" stroke="none">id 1234</tspan></tspan></tspan></text>
</g>
</g>
</g>
<g class="com.sun.star.drawing.ConnectorShape">
<g id="id7">
<rect class="BoundingBox" stroke="none" fill="none" x="6700" y="5450" width="1601" height="2651"/>
<path fill="none" stroke="rgb(0,0,0)" d="M 7130,5600 L 7500,5600 7500,7950 7870,7950"/>
<path fill="rgb(0,0,0)" stroke="none" d="M 6700,5600 L 7150,5750 7150,5450 6700,5600 Z"/>
<path fill="rgb(0,0,0)" stroke="none" d="M 8300,7950 L 7850,7800 7850,8100 8300,7950 Z"/>
</g>
</g>
<g class="Group">
<g class="Group">
<g class="com.sun.star.drawing.CustomShape">
<g id="id8">
<rect class="BoundingBox" stroke="none" fill="none" x="8299" y="4649" width="4753" height="6603"/>
<path fill="none" stroke="rgb(0,0,0)" d="M 10675,11250 L 8300,11250 8300,4650 13050,4650 13050,11250 10675,11250 Z"/>
</g>
</g>
<g class="com.sun.star.drawing.LineShape">
<g id="id9">
<rect class="BoundingBox" stroke="none" fill="none" x="8299" y="5677" width="4753" height="3"/>
<path fill="none" stroke="rgb(0,0,0)" d="M 8300,5678 L 13050,5678"/>
</g>
</g>
<g class="com.sun.star.drawing.TextShape">
<g id="id10">
<rect class="BoundingBox" stroke="none" fill="none" x="9500" y="4812" width="2687" height="718"/>
<text class="TextShape"><tspan class="TextParagraph" font-family="DejaVu Sans, sans-serif" font-size="353px" font-weight="400"><tspan class="TextPosition" x="9750" y="5263"><tspan fill="rgb(0,0,0)" stroke="none">joining table</tspan></tspan></tspan></text>
</g>
</g>
</g>
<g class="com.sun.star.drawing.TextShape">
<g id="id11">
<rect class="BoundingBox" stroke="none" fill="none" x="8529" y="7691" width="1881" height="665"/>
<path fill="none" stroke="rgb(0,0,0)" d="M 9469,8354 L 8530,8354 8530,7692 10408,7692 10408,8354 9469,8354 Z"/>
<text class="TextShape"><tspan class="TextParagraph" font-family="DejaVu Sans, sans-serif" font-size="353px" font-weight="400"><tspan class="TextPosition" x="8780" y="8143"><tspan fill="rgb(0,0,0)" stroke="none">id 1234</tspan></tspan></tspan></text>
</g>
</g>
<g class="com.sun.star.drawing.TextShape">
<g id="id12">
<rect class="BoundingBox" stroke="none" fill="none" x="10575" y="7688" width="2388" height="665"/>
<path fill="none" stroke="rgb(0,0,0)" d="M 11769,8351 L 10576,8351 10576,7689 12961,7689 12961,8351 11769,8351 Z"/>
<text class="TextShape"><tspan class="TextParagraph" font-family="DejaVu Sans, sans-serif" font-size="353px" font-weight="400"><tspan class="TextPosition" x="10826" y="8140"><tspan fill="rgb(0,0,0)" stroke="none">tagid 456</tspan></tspan></tspan></text>
</g>
</g>
</g>
<g class="Group">
<g class="Group">
<g class="com.sun.star.drawing.CustomShape">
<g id="id13">
<rect class="BoundingBox" stroke="none" fill="none" x="14299" y="2549" width="4753" height="6103"/>
<path fill="none" stroke="rgb(0,0,0)" d="M 16675,8650 L 14300,8650 14300,2550 19050,2550 19050,8650 16675,8650 Z"/>
</g>
</g>
<g class="com.sun.star.drawing.LineShape">
<g id="id14">
<rect class="BoundingBox" stroke="none" fill="none" x="14299" y="3499" width="4753" height="3"/>
<path fill="none" stroke="rgb(0,0,0)" d="M 14300,3500 L 19050,3500"/>
</g>
</g>
<g class="com.sun.star.drawing.TextShape">
<g id="id15">
<rect class="BoundingBox" stroke="none" fill="none" x="15500" y="2700" width="2268" height="663"/>
<text class="TextShape"><tspan class="TextParagraph" font-family="DejaVu Sans, sans-serif" font-size="353px" font-weight="400"><tspan class="TextPosition" x="15750" y="3151"><tspan fill="rgb(0,0,0)" stroke="none">tags table</tspan></tspan></tspan></text>
</g>
</g>
</g>
<g class="com.sun.star.drawing.TextShape">
<g id="id16">
<rect class="BoundingBox" stroke="none" fill="none" x="14543" y="4649" width="2312" height="665"/>
<path fill="none" stroke="rgb(0,0,0)" d="M 15699,5312 L 14544,5312 14544,4650 16853,4650 16853,5312 15699,5312 Z"/>
<text class="TextShape"><tspan class="TextParagraph" font-family="DejaVu Sans, sans-serif" font-size="353px" font-weight="400"><tspan class="TextPosition" x="14794" y="5101"><tspan fill="rgb(0,0,0)" stroke="none">tagid 456</tspan></tspan></tspan></text>
</g>
</g>
<g class="com.sun.star.drawing.TextShape">
<g id="id17">
<rect class="BoundingBox" stroke="none" fill="none" x="15386" y="5299" width="3066" height="665"/>
<path fill="none" stroke="rgb(0,0,0)" d="M 16919,5962 L 15387,5962 15387,5300 18450,5300 18450,5962 16919,5962 Z"/>
<text class="TextShape"><tspan class="TextParagraph" font-family="DejaVu Sans, sans-serif" font-size="353px" font-weight="400"><tspan class="TextPosition" x="15637" y="5751"><tspan fill="rgb(0,0,0)" stroke="none">tag banana</tspan></tspan></tspan></text>
</g>
</g>
</g>
<g class="com.sun.star.drawing.ConnectorShape">
<g id="id18">
<rect class="BoundingBox" stroke="none" fill="none" x="13050" y="5450" width="1251" height="2651"/>
<path fill="none" stroke="rgb(0,0,0)" d="M 13480,7950 L 13675,7950 13675,5600 13870,5600"/>
<path fill="rgb(0,0,0)" stroke="none" d="M 13050,7950 L 13500,8100 13500,7800 13050,7950 Z"/>
<path fill="rgb(0,0,0)" stroke="none" d="M 14300,5600 L 13850,5450 13850,5750 14300,5600 Z"/>
</g>
</g>
<g class="com.sun.star.drawing.CustomShape">
<g id="id19">
<rect class="BoundingBox" stroke="none" fill="none" x="1349" y="1449" width="18303" height="11503"/>
<path fill="none" stroke="rgb(0,0,0)" d="M 10500,12950 L 1350,12950 1350,1450 19650,1450 19650,12950 10500,12950 Z"/>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 20 KiB