Extract creation of intervals into IntervalFactory

This commit is contained in:
Thomas Lauf 2018-10-05 13:48:55 +02:00
parent 8008cd8312
commit 1a24c49507
9 changed files with 190 additions and 110 deletions

View file

@ -11,6 +11,7 @@ set (timew_SRCS CLI.cpp CLI.h
Exclusion.cpp Exclusion.h Exclusion.cpp Exclusion.h
Extensions.cpp Extensions.h Extensions.cpp Extensions.h
Interval.cpp Interval.h Interval.cpp Interval.h
IntervalFactory.cpp IntervalFactory.h
Range.cpp Range.h Range.cpp Range.h
Rules.cpp Rules.h Rules.cpp Rules.h
TagInfo.cpp TagInfo.h TagInfo.cpp TagInfo.h

View file

@ -1,6 +1,6 @@
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// //
// Copyright 2015 - 2016, Paul Beckingham, Federico Hernandez. // Copyright 2016 - 2018, Thomas Lauf, Paul Beckingham, Federico Hernandez.
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal // of this software and associated documentation files (the "Software"), to deal
@ -32,56 +32,6 @@
#include <sstream> #include <sstream>
#include <JSON.h> #include <JSON.h>
////////////////////////////////////////////////////////////////////////////////
// Syntax:
// 'inc' [ <iso> [ '-' <iso> ]] [ '#' <tag> [ <tag> ... ]]
void Interval::initialize (const std::string& line)
{
Lexer lexer (line);
std::vector <std::string> tokens;
std::string token;
Lexer::Type type;
while (lexer.token (token, type))
tokens.push_back (Lexer::dequote (token));
// Minimal requirement 'inc'.
if (!tokens.empty () &&
tokens[0] == "inc")
{
unsigned int offset = 0;
// Optional <iso>
if (tokens.size () > 1 &&
tokens[1].length () == 16)
{
start = Datetime (tokens[1]);
offset = 1;
// Optional '-' <iso>
if (tokens.size () > 3 &&
tokens[2] == "-" &&
tokens[3].length () == 16)
{
end = Datetime (tokens[3]);
offset = 3;
}
}
// Optional '#' <tag>
if (tokens.size () > 2 + offset &&
tokens[1 + offset] == "#")
{
// Optional <tag> ...
for (unsigned int i = 2 + offset; i < tokens.size (); ++i)
_tags.insert (tokens[i]);
}
return;
}
throw format ("Unrecognizable line '{1}'.", line);
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
bool Interval::empty () const bool Interval::empty () const
{ {
@ -218,32 +168,5 @@ void Interval::setRange (const Datetime& start, const Datetime& end)
this->end = end; this->end = end;
} }
Interval Interval::fromJson (std::string jsonString)
{
Interval interval = Interval ();
if (!jsonString.empty ())
{
auto* json = (json::object*) json::parse (jsonString);
json::array* tags = (json::array*) json->_data["tags"];
if (tags != nullptr)
{
for (auto& tag : tags->_data)
{
auto* value = (json::string*) tag;
interval.tag(value->_data);
}
}
json::string* start = (json::string*) json->_data["start"];
interval.start = Datetime(start->_data);
json::string* end = (json::string*) json->_data["end"];
interval.end = (end != nullptr) ? Datetime(end->_data) : 0;
}
return interval;
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////

View file

@ -1,6 +1,6 @@
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// //
// Copyright 2015 - 2016, Paul Beckingham, Federico Hernandez. // Copyright 2016 - 2018, Thomas Lauf, Paul Beckingham, Federico Hernandez.
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal // of this software and associated documentation files (the "Software"), to deal
@ -35,7 +35,6 @@ class Interval : public Range
{ {
public: public:
Interval () = default; Interval () = default;
void initialize (const std::string&);
bool empty () const; bool empty () const;
bool hasTag (const std::string&) const; bool hasTag (const std::string&) const;
@ -50,8 +49,6 @@ public:
std::string json () const; std::string json () const;
std::string dump () const; std::string dump () const;
static Interval fromJson (std::string json);
public: public:
int id {0}; int id {0};
bool synthetic {false}; bool synthetic {false};

116
src/IntervalFactory.cpp Normal file
View file

@ -0,0 +1,116 @@
////////////////////////////////////////////////////////////////////////////////
//
// Copyright 2018, Thomas Lauf, Paul Beckingham, Federico Hernandez.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// https://www.opensource.org/licenses/mit-license.php
//
////////////////////////////////////////////////////////////////////////////////
#include <format.h>
#include <Lexer.h>
#include <IntervalFactory.h>
#include <JSON.h>
////////////////////////////////////////////////////////////////////////////////
// Syntax:
// 'inc' [ <iso> [ '-' <iso> ]] [ '#' <tag> [ <tag> ... ]]
Interval IntervalFactory::fromSerialization (const std::string &line)
{
Lexer lexer (line);
std::vector <std::string> tokens;
std::string token;
Lexer::Type type;
while (lexer.token (token, type))
{
tokens.push_back (Lexer::dequote (token));
}
// Minimal requirement 'inc'.
if (!tokens.empty () &&
tokens[0] == "inc")
{
Interval interval = Interval ();
unsigned int offset = 0;
// Optional <iso>
if (tokens.size () > 1 &&
tokens[1].length () == 16)
{
interval.start = Datetime (tokens[1]);
offset = 1;
// Optional '-' <iso>
if (tokens.size () > 3 &&
tokens[2] == "-" &&
tokens[3].length () == 16)
{
interval.end = Datetime (tokens[3]);
offset = 3;
}
}
// Optional '#' <tag>
if (tokens.size () > 2 + offset &&
tokens[1 + offset] == "#")
{
// Optional <tag> ...
for (unsigned int i = 2 + offset; i < tokens.size (); ++i)
interval.tag (tokens[i]);
}
return interval;
}
throw format ("Unrecognizable line '{1}'.", line);
}
////////////////////////////////////////////////////////////////////////////////
Interval IntervalFactory::fromJson (std::string jsonString)
{
Interval interval = Interval ();
if (!jsonString.empty ())
{
auto* json = (json::object*) json::parse (jsonString);
json::array* tags = (json::array*) json->_data["tags"];
if (tags != nullptr)
{
for (auto& tag : tags->_data)
{
auto* value = (json::string*) tag;
interval.tag(value->_data);
}
}
json::string* start = (json::string*) json->_data["start"];
interval.start = Datetime(start->_data);
json::string* end = (json::string*) json->_data["end"];
interval.end = (end != nullptr) ? Datetime(end->_data) : 0;
}
return interval;
}
////////////////////////////////////////////////////////////////////////////////

40
src/IntervalFactory.h Normal file
View file

@ -0,0 +1,40 @@
////////////////////////////////////////////////////////////////////////////////
//
// Copyright 2018, Thomas Lauf, Paul Beckingham, Federico Hernandez.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// https://www.opensource.org/licenses/mit-license.php
//
////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDED_INTERVALFACTORY
#define INCLUDED_INTERVALFACTORY
#include <Interval.h>
#include <string>
class IntervalFactory
{
public:
static Interval fromSerialization (const std::string &line);
static Interval fromJson (std::string jsonString);
};
#endif

View file

@ -1,6 +1,6 @@
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// //
// Copyright 2018, Thomas Lauf, Paul Beckingham, Federico Hernandez. // Copyright 2016, 2018, Thomas Lauf, Paul Beckingham, Federico Hernandez.
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal // of this software and associated documentation files (the "Software"), to deal
@ -28,11 +28,12 @@
#include <timew.h> #include <timew.h>
#include <iostream> #include <iostream>
#include <format.h> #include <format.h>
#include <IntervalFactory.h>
static void undoIntervalAction(UndoAction& action, Database& database) static void undoIntervalAction(UndoAction& action, Database& database)
{ {
Interval before = Interval::fromJson (action.getBefore ()); Interval before = IntervalFactory::fromJson (action.getBefore ());
Interval after = Interval::fromJson (action.getAfter ()); Interval after = IntervalFactory::fromJson (action.getAfter ());
database.modifyInterval (after, before, false); database.modifyInterval (after, before, false);
} }

View file

@ -1,6 +1,6 @@
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// //
// Copyright 2015 - 2018, Thomas Lauf, Paul Beckingham, Federico Hernandez. // Copyright 2016 - 2018, Thomas Lauf, Paul Beckingham, Federico Hernandez.
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal // of this software and associated documentation files (the "Software"), to deal
@ -32,6 +32,7 @@
#include <timew.h> #include <timew.h>
#include <algorithm> #include <algorithm>
#include <iostream> #include <iostream>
#include "IntervalFactory.h"
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// A filter is just another interval, containing start, end and tags. // A filter is just another interval, containing start, end and tags.
@ -316,8 +317,7 @@ std::vector <Interval> getAllInclusions (Database& database)
std::vector <Interval> all; std::vector <Interval> all;
for (auto& line : database.allLines ()) for (auto& line : database.allLines ())
{ {
Interval i; Interval i = IntervalFactory::fromSerialization (line);
i.initialize (line);
all.push_back (i); all.push_back (i);
} }
@ -675,14 +675,14 @@ Interval getLatestInterval (Database& database)
// ^ 20 // ^ 20
if (line.find (" - ") != 20) if (line.find (" - ") != 20)
{ {
i.initialize (line); i = IntervalFactory::fromSerialization (line);
return i; return i;
} }
} }
auto lastLine = database.lastLine (); auto lastLine = database.lastLine ();
if (! lastLine.empty ()) if (! lastLine.empty ())
i.initialize (lastLine); i = IntervalFactory::fromSerialization (lastLine);
return i; return i;
} }

View file

@ -1,6 +1,6 @@
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// //
// Copyright 2015 - 2018, Paul Beckingham, Federico Hernandez. // Copyright 2016 - 2018, Thomas Lauf, Paul Beckingham, Federico Hernandez.
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal // of this software and associated documentation files (the "Software"), to deal
@ -26,6 +26,7 @@
#include <timew.h> #include <timew.h>
#include <test.h> #include <test.h>
#include <src/IntervalFactory.h>
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
void test_flatten ( void test_flatten (
@ -36,7 +37,7 @@ void test_flatten (
const std::vector <std::string>& output) const std::vector <std::string>& output)
{ {
Interval i; Interval i;
i.initialize (input); i = IntervalFactory::fromSerialization (input);
auto results = flatten (i, exclusions); auto results = flatten (i, exclusions);
@ -44,7 +45,7 @@ void test_flatten (
for (unsigned int i = 0; i < std::min (output.size (), results.size ()); ++i) for (unsigned int i = 0; i < std::min (output.size (), results.size ()); ++i)
{ {
Interval tmp; Interval tmp;
tmp.initialize (output[i]); tmp = IntervalFactory::fromSerialization (output[i]);
t.is (tmp.start.toISO (), results[i].start.toISO (), "flatten: " + label + " start matches"); t.is (tmp.start.toISO (), results[i].start.toISO (), "flatten: " + label + " start matches");
t.is (tmp.end.toISO (), results[i].end.toISO (), "flatten: " + label + " end matches"); t.is (tmp.end.toISO (), results[i].end.toISO (), "flatten: " + label + " end matches");

View file

@ -1,6 +1,6 @@
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// //
// Copyright 2015 - 2018, Paul Beckingham, Federico Hernandez. // Copyright 2016 - 2018, Thomas Lauf, Paul Beckingham, Federico Hernandez.
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal // of this software and associated documentation files (the "Software"), to deal
@ -26,6 +26,7 @@
#include <cmake.h> #include <cmake.h>
#include <Interval.h> #include <Interval.h>
#include <IntervalFactory.h>
#include <test.h> #include <test.h>
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@ -82,83 +83,83 @@ int main (int, char**)
// Round-trip parsing. // Round-trip parsing.
Interval i4; Interval i4;
i4.initialize ( "inc"); i4 = IntervalFactory::fromSerialization ("inc");
t.is (i4.serialize (), "inc", t.is (i4.serialize (), "inc",
"Round-trip 'inc'"); "Round-trip 'inc'");
Interval i5; Interval i5;
i5.initialize ( "inc # foo"); i5 = IntervalFactory::fromSerialization ("inc # foo");
t.is (i5.serialize (), "inc # foo", t.is (i5.serialize (), "inc # foo",
"Round-trip 'inc # foo'"); "Round-trip 'inc # foo'");
Interval i6; Interval i6;
i6.initialize ( "inc # bar foo"); i6 = IntervalFactory::fromSerialization ("inc # bar foo");
t.is (i6.serialize (), "inc # bar foo", t.is (i6.serialize (), "inc # bar foo",
"Round-trip 'inc # bar foo'"); "Round-trip 'inc # bar foo'");
Interval i7; Interval i7;
i7.initialize ( "inc 19700101T000001Z"); i7 = IntervalFactory::fromSerialization ("inc 19700101T000001Z");
t.is (i7.serialize (), "inc 19700101T000001Z", t.is (i7.serialize (), "inc 19700101T000001Z",
"Round-trip 'inc 19700101T000001Z'"); "Round-trip 'inc 19700101T000001Z'");
Interval i8; Interval i8;
i8.initialize ( "inc 19700101T000001Z - 19700101T000002Z"); i8 = IntervalFactory::fromSerialization ("inc 19700101T000001Z - 19700101T000002Z");
t.is (i8.serialize (), "inc 19700101T000001Z - 19700101T000002Z", t.is (i8.serialize (), "inc 19700101T000001Z - 19700101T000002Z",
"Round-trip 'inc 19700101T000001Z - 19700101T000002Z'"); "Round-trip 'inc 19700101T000001Z - 19700101T000002Z'");
Interval i9; Interval i9;
i9.initialize ( "inc 19700101T000001Z # bar foo"); i9 = IntervalFactory::fromSerialization ("inc 19700101T000001Z # bar foo");
t.is (i9.serialize (), "inc 19700101T000001Z # bar foo", t.is (i9.serialize (), "inc 19700101T000001Z # bar foo",
"Round-trip 'inc 19700101T000001Z # bar foo'"); "Round-trip 'inc 19700101T000001Z # bar foo'");
Interval i10; Interval i10;
i10.initialize ( "inc 19700101T000001Z - 19700101T000002Z # bar foo"); i10 = IntervalFactory::fromSerialization ("inc 19700101T000001Z - 19700101T000002Z # bar foo");
t.is (i10.serialize (), "inc 19700101T000001Z - 19700101T000002Z # bar foo", t.is (i10.serialize (), "inc 19700101T000001Z - 19700101T000002Z # bar foo",
"Round-trip 'inc 19700101T000001Z - 19700101T000002Z # bar foo'"); "Round-trip 'inc 19700101T000001Z - 19700101T000002Z # bar foo'");
Interval i11; Interval i11;
i11.initialize ( "inc 19700101T000001Z - 19700101T000002Z # \"Trans-Europe Express\" bar foo"); i11 = IntervalFactory::fromSerialization ("inc 19700101T000001Z - 19700101T000002Z # \"Trans-Europe Express\" bar foo");
t.is (i11.serialize (), "inc 19700101T000001Z - 19700101T000002Z # \"Trans-Europe Express\" bar foo", t.is (i11.serialize (), "inc 19700101T000001Z - 19700101T000002Z # \"Trans-Europe Express\" bar foo",
"Round-trip 'inc 19700101T000001Z - 19700101T000002Z # \"Trans-Europe Express\" bar foo'"); "Round-trip 'inc 19700101T000001Z - 19700101T000002Z # \"Trans-Europe Express\" bar foo'");
// std::string json () const; // std::string json () const;
Interval i12; Interval i12;
i12.initialize ( "inc"); i12 = IntervalFactory::fromSerialization ("inc");
t.is (i12.json (), "{}", t.is (i12.json (), "{}",
"JSON '{}'"); "JSON '{}'");
Interval i13; Interval i13;
i13.initialize ( "inc # foo"); i13 = IntervalFactory::fromSerialization ("inc # foo");
t.is (i13.json (), "{\"tags\":[\"foo\"]}", t.is (i13.json (), "{\"tags\":[\"foo\"]}",
"JSON '{\"tags\":[\"foo\"]}'"); "JSON '{\"tags\":[\"foo\"]}'");
Interval i14; Interval i14;
i14.initialize ( "inc # bar foo"); i14 = IntervalFactory::fromSerialization ("inc # bar foo");
t.is (i14.json (), "{\"tags\":[\"bar\",\"foo\"]}", t.is (i14.json (), "{\"tags\":[\"bar\",\"foo\"]}",
"JSON '{\"tags\":[\"bar\",\"foo\"]}'"); "JSON '{\"tags\":[\"bar\",\"foo\"]}'");
Interval i15; Interval i15;
i15.initialize ( "inc 19700101T000001Z"); i15 = IntervalFactory::fromSerialization ("inc 19700101T000001Z");
t.is (i15.json (), "{\"start\":\"19700101T000001Z\"}", t.is (i15.json (), "{\"start\":\"19700101T000001Z\"}",
"JSON '{\"start\":\"19700101T000001Z\"}'"); "JSON '{\"start\":\"19700101T000001Z\"}'");
Interval i16; Interval i16;
i16.initialize ( "inc 19700101T000001Z - 19700101T000002Z"); i16 = IntervalFactory::fromSerialization ("inc 19700101T000001Z - 19700101T000002Z");
t.is (i16.json (), "{\"start\":\"19700101T000001Z\",\"end\":\"19700101T000002Z\"}", t.is (i16.json (), "{\"start\":\"19700101T000001Z\",\"end\":\"19700101T000002Z\"}",
"JSON '{\"start\":\"19700101T000001Z\",\"end\":\"19700101T000002Z\"}'"); "JSON '{\"start\":\"19700101T000001Z\",\"end\":\"19700101T000002Z\"}'");
Interval i17; Interval i17;
i17.initialize ( "inc 19700101T000001Z # bar foo"); i17 = IntervalFactory::fromSerialization ("inc 19700101T000001Z # bar foo");
t.is (i17.json (), "{\"start\":\"19700101T000001Z\",\"tags\":[\"bar\",\"foo\"]}", t.is (i17.json (), "{\"start\":\"19700101T000001Z\",\"tags\":[\"bar\",\"foo\"]}",
"JSON '{\"start\":\"19700101T000001Z\",\"tags\":[\"bar\",\"foo\"]}'"); "JSON '{\"start\":\"19700101T000001Z\",\"tags\":[\"bar\",\"foo\"]}'");
Interval i18; Interval i18;
i18.initialize ( "inc 19700101T000001Z - 19700101T000002Z # bar foo"); i18 = IntervalFactory::fromSerialization ("inc 19700101T000001Z - 19700101T000002Z # bar foo");
t.is (i18.json (), "{\"start\":\"19700101T000001Z\",\"end\":\"19700101T000002Z\",\"tags\":[\"bar\",\"foo\"]}", t.is (i18.json (), "{\"start\":\"19700101T000001Z\",\"end\":\"19700101T000002Z\",\"tags\":[\"bar\",\"foo\"]}",
"JSON '{\"start\":\"19700101T000001Z\",\"end\":\"19700101T000002Z\",\"tags\":[\"bar\",\"foo\"]}'"); "JSON '{\"start\":\"19700101T000001Z\",\"end\":\"19700101T000002Z\",\"tags\":[\"bar\",\"foo\"]}'");
Interval i19; Interval i19;
i19.initialize ( "inc 19700101T000001Z - 19700101T000002Z # \"Trans-Europe Express\" bar foo"); i19 = IntervalFactory::fromSerialization ("inc 19700101T000001Z - 19700101T000002Z # \"Trans-Europe Express\" bar foo");
t.is (i19.json (), "{\"start\":\"19700101T000001Z\",\"end\":\"19700101T000002Z\",\"tags\":[\"Trans-Europe Express\",\"bar\",\"foo\"]}", t.is (i19.json (), "{\"start\":\"19700101T000001Z\",\"end\":\"19700101T000002Z\",\"tags\":[\"Trans-Europe Express\",\"bar\",\"foo\"]}",
"JSON '{\"start\":\"19700101T000001Z\",\"end\":\"19700101T000002Z\",\"tags\":[\"Trans-Europe Express\",\"bar\",\"foo\"]}'"); "JSON '{\"start\":\"19700101T000001Z\",\"end\":\"19700101T000002Z\",\"tags\":[\"Trans-Europe Express\",\"bar\",\"foo\"]}'");