diff options
Diffstat (limited to 'src/rdf.cpp')
-rw-r--r-- | src/rdf.cpp | 1233 |
1 files changed, 1233 insertions, 0 deletions
diff --git a/src/rdf.cpp b/src/rdf.cpp new file mode 100644 index 0000000..3aeb0af --- /dev/null +++ b/src/rdf.cpp @@ -0,0 +1,1233 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * RDF manipulation functions. + * + * @todo move these to xml/ instead of dialogs/ + */ +/* Authors: + * Kees Cook <kees@outflux.net> + * Jon Phillips <jon@rejon.org> + * Jon A. Cruz <jon@joncruz.org> + * + * Copyright (C) 2004 Kees Cook <kees@outflux.net> + * Copyright (C) 2006 Jon Phillips <jon@rejon.org> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "inkscape.h" +#include "preferences.h" +#include "rdf.h" + +#include "object/sp-item-group.h" +#include "object/sp-root.h" + +#include "xml/repr.h" + +/* + Example RDF XML from various places... + +<rdf:RDF xmlns="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> +<Work rdf:about=""> + <dc:title>title of work</dc:title> + <dc:date>year</dc:date> + <dc:description>description of work</dc:description> + <dc:creator><Agent> + <dc:title>creator</dc:title> + </Agent></dc:creator> + <dc:rights><Agent> + <dc:title>holder</dc:title> + </Agent></dc:rights> + <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:source rdf:resource="source"/> + <license rdf:resource="http://creativecommons.org/licenses/by/4.0/" +/> +</Work> + + + <rdf:RDF xmlns="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> + <Work rdf:about=""> + <dc:title>SVG Road Signs</dc:title> + <dc:rights><Agent> + <dc:title>John Cliff</dc:title> + </Agent></dc:rights> + <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <license rdf:resource="http://creativecommons.org/ns#PublicDomain" /> + </Work> + + <License rdf:about="http://creativecommons.org/ns#PublicDomain"> + <permits rdf:resource="http://creativecommons.org/ns#Reproduction" /> + <permits rdf:resource="http://creativecommons.org/ns#Distribution" /> + <permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks" /> + </License> + +</rdf:RDF> + + +Bag example: + +<dc:subject> +<rdf:Bag> +<rdf:li>open clip art logo</rdf:li> +<rdf:li>images</rdf:li> +<rdf:li>logo</rdf:li> +<rdf:li>clip art</rdf:li> +<rdf:li>ocal</rdf:li> +<rdf:li>logotype</rdf:li> +<rdf:li>filetype</rdf:li> +</rdf:Bag> +</dc:subject> +*/ + +// clang-format off +struct rdf_double_t rdf_license_empty [] = { + { nullptr, nullptr } +}; + +struct rdf_double_t rdf_license_cc_a [] = { + { "cc:permits", "http://creativecommons.org/ns#Reproduction", }, + { "cc:permits", "http://creativecommons.org/ns#Distribution", }, + { "cc:requires", "http://creativecommons.org/ns#Notice", }, + { "cc:requires", "http://creativecommons.org/ns#Attribution", }, + { "cc:permits", "http://creativecommons.org/ns#DerivativeWorks", }, + { nullptr, nullptr } +}; + +struct rdf_double_t rdf_license_cc_a_sa [] = { + { "cc:permits", "http://creativecommons.org/ns#Reproduction", }, + { "cc:permits", "http://creativecommons.org/ns#Distribution", }, + { "cc:requires", "http://creativecommons.org/ns#Notice", }, + { "cc:requires", "http://creativecommons.org/ns#Attribution", }, + { "cc:permits", "http://creativecommons.org/ns#DerivativeWorks", }, + { "cc:requires", "http://creativecommons.org/ns#ShareAlike", }, + { nullptr, nullptr } +}; + +struct rdf_double_t rdf_license_cc_a_nd [] = { + { "cc:permits", "http://creativecommons.org/ns#Reproduction", }, + { "cc:permits", "http://creativecommons.org/ns#Distribution", }, + { "cc:requires", "http://creativecommons.org/ns#Notice", }, + { "cc:requires", "http://creativecommons.org/ns#Attribution", }, + { nullptr, nullptr } +}; + +struct rdf_double_t rdf_license_cc_a_nc [] = { + { "cc:permits", "http://creativecommons.org/ns#Reproduction", }, + { "cc:permits", "http://creativecommons.org/ns#Distribution", }, + { "cc:requires", "http://creativecommons.org/ns#Notice", }, + { "cc:requires", "http://creativecommons.org/ns#Attribution", }, + { "cc:prohibits", "http://creativecommons.org/ns#CommercialUse", }, + { "cc:permits", "http://creativecommons.org/ns#DerivativeWorks", }, + { nullptr, nullptr } +}; + +struct rdf_double_t rdf_license_cc_a_nc_sa [] = { + { "cc:permits", "http://creativecommons.org/ns#Reproduction", }, + { "cc:permits", "http://creativecommons.org/ns#Distribution", }, + { "cc:requires", "http://creativecommons.org/ns#Notice", }, + { "cc:requires", "http://creativecommons.org/ns#Attribution", }, + { "cc:prohibits", "http://creativecommons.org/ns#CommercialUse", }, + { "cc:permits", "http://creativecommons.org/ns#DerivativeWorks", }, + { "cc:requires", "http://creativecommons.org/ns#ShareAlike", }, + { nullptr, nullptr } +}; + +struct rdf_double_t rdf_license_cc_a_nc_nd [] = { + { "cc:permits", "http://creativecommons.org/ns#Reproduction", }, + { "cc:permits", "http://creativecommons.org/ns#Distribution", }, + { "cc:requires", "http://creativecommons.org/ns#Notice", }, + { "cc:requires", "http://creativecommons.org/ns#Attribution", }, + { "cc:prohibits", "http://creativecommons.org/ns#CommercialUse", }, + { nullptr, nullptr } +}; + +struct rdf_double_t rdf_license_pd [] = { + { "cc:permits", "http://creativecommons.org/ns#Reproduction", }, + { "cc:permits", "http://creativecommons.org/ns#Distribution", }, + { "cc:permits", "http://creativecommons.org/ns#DerivativeWorks", }, + { nullptr, nullptr } +}; + +struct rdf_double_t rdf_license_freeart [] = { + { "cc:permits", "http://creativecommons.org/ns#Reproduction", }, + { "cc:permits", "http://creativecommons.org/ns#Distribution", }, + { "cc:permits", "http://creativecommons.org/ns#DerivativeWorks", }, + { "cc:requires", "http://creativecommons.org/ns#ShareAlike", }, + { "cc:requires", "http://creativecommons.org/ns#Notice", }, + { "cc:requires", "http://creativecommons.org/ns#Attribution", }, + { nullptr, nullptr } +}; + +struct rdf_double_t rdf_license_ofl [] = { + { "cc:permits", "http://scripts.sil.org/pub/OFL/Reproduction", }, + { "cc:permits", "http://scripts.sil.org/pub/OFL/Distribution", }, + { "cc:permits", "http://scripts.sil.org/pub/OFL/Embedding", }, + { "cc:permits", "http://scripts.sil.org/pub/OFL/DerivativeWorks", }, + { "cc:requires", "http://scripts.sil.org/pub/OFL/Notice", }, + { "cc:requires", "http://scripts.sil.org/pub/OFL/Attribution", }, + { "cc:requires", "http://scripts.sil.org/pub/OFL/ShareAlike", }, + { "cc:requires", "http://scripts.sil.org/pub/OFL/DerivativeRenaming", }, + { "cc:requires", "http://scripts.sil.org/pub/OFL/BundlingWhenSelling", }, + { nullptr, nullptr } +}; + +struct rdf_license_t rdf_licenses [] = { + { N_("CC Attribution"), + "http://creativecommons.org/licenses/by/4.0/", + rdf_license_cc_a, + }, + + { N_("CC Attribution-ShareAlike"), + "http://creativecommons.org/licenses/by-sa/4.0/", + rdf_license_cc_a_sa, + }, + + { N_("CC Attribution-NoDerivs"), + "http://creativecommons.org/licenses/by-nd/4.0/", + rdf_license_cc_a_nd, + }, + + { N_("CC Attribution-NonCommercial"), + "http://creativecommons.org/licenses/by-nc/4.0/", + rdf_license_cc_a_nc, + }, + + { N_("CC Attribution-NonCommercial-ShareAlike"), + "http://creativecommons.org/licenses/by-nc-sa/4.0/", + rdf_license_cc_a_nc_sa, + }, + + { N_("CC Attribution-NonCommercial-NoDerivs"), + "http://creativecommons.org/licenses/by-nc-nd/4.0/", + rdf_license_cc_a_nc_nd, + }, + + { N_("CC0 Public Domain Dedication"), + "http://creativecommons.org/publicdomain/zero/1.0/", + rdf_license_pd, + }, + + { N_("FreeArt"), + "http://artlibre.org/licence/lal", + rdf_license_freeart, + }, + + { N_("Open Font License"), + "http://scripts.sil.org/OFL", + rdf_license_ofl, + }, + + { nullptr, nullptr, rdf_license_empty, } +}; +// clang-format on + +#define XML_TAG_NAME_SVG "svg:svg" +#define XML_TAG_NAME_METADATA "svg:metadata" +#define XML_TAG_NAME_RDF "rdf:RDF" +#define XML_TAG_NAME_WORK "cc:Work" +#define XML_TAG_NAME_LICENSE "cc:License" +// Note the lowercase L! +#define XML_TAG_NAME_LICENSE_PROP "cc:license" + + +// Remember when using the "title" and "tip" elements to pass them through +// the localization functions when you use them! +struct rdf_work_entity_t rdf_work_entities [] = { + // clang-format off + { "title", N_("Title:"), "dc:title", RDF_CONTENT, + N_("A name given to the resource"), RDF_FORMAT_LINE, RDF_EDIT_GENERIC, + }, + { "date", N_("Date:"), "dc:date", RDF_CONTENT, + N_("A point or period of time associated with an event in the lifecycle of the resource"), RDF_FORMAT_LINE, RDF_EDIT_GENERIC, + }, + { "format", N_("Format:"), "dc:format", RDF_CONTENT, + N_("The file format, physical medium, or dimensions of the resource"), RDF_FORMAT_LINE, RDF_EDIT_HARDCODED, + }, + { "type", N_("Type:"), "dc:type", RDF_RESOURCE, + N_("The nature or genre of the resource"), RDF_FORMAT_LINE, RDF_EDIT_HARDCODED, + }, + + { "creator", N_("Creator:"), "dc:creator", RDF_AGENT, + N_("An entity primarily responsible for making the resource"), RDF_FORMAT_LINE, RDF_EDIT_GENERIC, + }, + { "rights", N_("Rights:"), "dc:rights", RDF_AGENT, + N_("Information about rights held in and over the resource"), RDF_FORMAT_LINE, RDF_EDIT_GENERIC, + }, + { "publisher", N_("Publisher:"), "dc:publisher", RDF_AGENT, + N_("An entity responsible for making the resource available"), RDF_FORMAT_LINE, RDF_EDIT_GENERIC, + }, + + { "identifier", N_("Identifier:"), "dc:identifier", RDF_CONTENT, + N_("An unambiguous reference to the resource within a given context"), RDF_FORMAT_LINE, RDF_EDIT_GENERIC, + }, + { "source", N_("Source:"), "dc:source", RDF_CONTENT, + N_("A related resource from which the described resource is derived"), RDF_FORMAT_LINE, RDF_EDIT_GENERIC, + }, + { "relation", N_("Relation:"), "dc:relation", RDF_CONTENT, + N_("A related resource"), RDF_FORMAT_LINE, RDF_EDIT_GENERIC, + }, + { "language", N_("Language:"), "dc:language", RDF_CONTENT, + N_("A language of the resource"), RDF_FORMAT_LINE, RDF_EDIT_GENERIC, + }, + { "subject", N_("Keywords:"), "dc:subject", RDF_BAG, + N_("The topic of the resource"), RDF_FORMAT_LINE, RDF_EDIT_GENERIC, + }, + // TRANSLATORS: "Coverage": the spatial or temporal characteristics of the content. + // For info, see Appendix D of http://www.w3.org/TR/1998/WD-rdf-schema-19980409/ + { "coverage", N_("Coverage:"), "dc:coverage", RDF_CONTENT, + N_("The spatial or temporal topic of the resource, the spatial applicability of the resource, or the jurisdiction under which the resource is relevant"), RDF_FORMAT_LINE, RDF_EDIT_GENERIC, + }, + + { "description", N_("Description:"), "dc:description", RDF_CONTENT, + N_("An account of the resource"), RDF_FORMAT_MULTILINE, RDF_EDIT_GENERIC, + }, + + // FIXME: need to handle 1 agent per line of input + { "contributor", N_("Contributors:"), "dc:contributor", RDF_AGENT, + N_("An entity responsible for making contributions to the resource"), RDF_FORMAT_MULTILINE, RDF_EDIT_GENERIC, + }, + + // TRANSLATORS: URL to a page that defines the license for the document + { "license_uri", N_("URI:"), "cc:license", RDF_RESOURCE, + // TRANSLATORS: this is where you put a URL to a page that defines the license + N_("URI to this document's license's namespace definition"), RDF_FORMAT_LINE, RDF_EDIT_SPECIAL, + }, + + // TRANSLATORS: fragment of XML representing the license of the document + { "license_fragment", N_("Fragment:"), "License", RDF_XML, + N_("XML fragment for the RDF 'License' section"), RDF_FORMAT_MULTILINE, RDF_EDIT_SPECIAL, + }, + + { nullptr, nullptr, nullptr, RDF_CONTENT, + nullptr, RDF_FORMAT_LINE, RDF_EDIT_HARDCODED, + } + // clang-format on +}; + + +// Simple start of C++-ification: +class RDFImpl +{ +public: + /** + * Some implementations do not put RDF stuff inside <metadata>, + * so we need to check for it and add it if we don't see it. + */ + static void ensureParentIsMetadata( SPDocument *doc, Inkscape::XML::Node *node ); + + static Inkscape::XML::Node const *getRdfRootRepr( SPDocument const * doc ); + static Inkscape::XML::Node *ensureRdfRootRepr( SPDocument * doc ); + + static Inkscape::XML::Node const *getXmlRepr( SPDocument const * doc, gchar const * name ); + static Inkscape::XML::Node *getXmlRepr( SPDocument * doc, gchar const * name ); + static Inkscape::XML::Node *ensureXmlRepr( SPDocument * doc, gchar const * name ); + + static Inkscape::XML::Node const *getWorkRepr( SPDocument const * doc, gchar const * name ); + static Inkscape::XML::Node *ensureWorkRepr( SPDocument * doc, gchar const * name ); + + static const gchar *getWorkEntity(SPDocument const * doc, struct rdf_work_entity_t & entity); + static unsigned int setWorkEntity(SPDocument * doc, struct rdf_work_entity_t & entity, gchar const * text); + + static void setDefaults( SPDocument * doc ); + + /** + * Pull the text out of an RDF entity, depends on how it's stored. + * + * @return A pointer to the entity's static contents as a string + * @param repr The XML element to extract from + * @param entity The desired RDF/Work entity + * + */ + static const gchar *getReprText( Inkscape::XML::Node const * repr, struct rdf_work_entity_t const & entity ); + + static unsigned int setReprText( Inkscape::XML::Node * repr, + struct rdf_work_entity_t const & entity, + gchar const * text ); + + static struct rdf_license_t *getLicense(SPDocument *document); + + static void setLicense(SPDocument * doc, struct rdf_license_t const * license); +}; + +/** + * Retrieves a known RDF/Work entity by name. + * + * @return A pointer to an RDF/Work entity + * @param name The desired RDF/Work entity + * + */ +struct rdf_work_entity_t *rdf_find_entity(gchar const * name) +{ + struct rdf_work_entity_t *entity; + for (entity=rdf_work_entities; entity->name; entity++) { + if (strcmp(entity->name,name)==0) break; + } + if (entity->name) return entity; + return nullptr; +} + +/* + * Takes the inkscape rdf struct and spits out a static RDF, which is only + * useful for testing. We must merge the rdf struct into the XML DOM for + * changes to be saved. + */ +/* + + Since g_markup_printf_escaped doesn't exist for most people's glib + right now, this function will remain commented out since it's only + for generic debug anyway. --Kees + +gchar * +rdf_string(struct rdf_t * rdf) +{ + gulong overall=0; + gchar *string=NULL; + + gchar *rdf_head="\ +<rdf:RDF xmlns=\"http://creativecommons.org/ns#\"\ + xmlns:dc=\"http://purl.org/dc/elements/1.1/\"\ + xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\ +"; + gchar *work_head="\ +<Work rdf:about=\"\">\ + <dc:type rdf:resource=\"http://purl.org/dc/dcmitype/StillImage\" />\ +"; + gchar *work_title=NULL; + gchar *work_date=NULL; + gchar *work_description=NULL; + gchar *work_creator=NULL; + gchar *work_owner=NULL; + gchar *work_source=NULL; + gchar *work_license=NULL; + gchar *license_head=NULL; + gchar *license=NULL; + gchar *license_end="</License>\n"; + gchar *work_end="</Work>\n"; + gchar *rdf_end="</rdf:RDF>\n"; + + if (rdf && rdf->work_title && rdf->work_title[0]) { + work_title=g_markup_printf_escaped(" <dc:title>%s</dc:title>\n", + rdf->work_title); + overall+=strlen(work_title); + } + if (rdf && rdf->work_date && rdf->work_date[0]) { + work_date=g_markup_printf_escaped(" <dc:date>%s</dc:date>\n", + rdf->work_date); + overall+=strlen(work_date); + } + if (rdf && rdf->work_description && rdf->work_description[0]) { + work_description=g_markup_printf_escaped(" <dc:description>%s</dc:description>\n", + rdf->work_description); + overall+=strlen(work_description); + } + if (rdf && rdf->work_creator && rdf->work_creator[0]) { + work_creator=g_markup_printf_escaped(" <dc:creator><Agent>\ + <dc:title>%s</dc:title>\ + </Agent></dc:creator>\n", + rdf->work_creator); + overall+=strlen(work_creator); + } + if (rdf && rdf->work_owner && rdf->work_owner[0]) { + work_owner=g_markup_printf_escaped(" <dc:rights><Agent>\ + <dc:title>%s</dc:title>\ + </Agent></dc:rights>\n", + rdf->work_owner); + overall+=strlen(work_owner); + } + if (rdf && rdf->work_source && rdf->work_source[0]) { + work_source=g_markup_printf_escaped(" <dc:source rdf:resource=\"%s\" />\n", + rdf->work_source); + overall+=strlen(work_source); + } + if (rdf && rdf->license && rdf->license->work_rdf && rdf->license->work_rdf[0]) { + work_license=g_markup_printf_escaped(" <license rdf:resource=\"%s\" />\n", + rdf->license->work_rdf); + overall+=strlen(work_license); + + license_head=g_markup_printf_escaped("<License rdf:about=\"%s\">\n", + rdf->license->work_rdf); + overall+=strlen(license_head); + overall+=strlen(rdf->license->license_rdf); + overall+=strlen(license_end); + } + + overall+=strlen(rdf_head)+strlen(rdf_end); + overall+=strlen(work_head)+strlen(work_end); + + overall++; // NULL term + + if (!(string=(gchar*)g_malloc(overall))) { + return NULL; + } + + string[0]='\0'; + strcat(string,rdf_head); + strcat(string,work_head); + + if (work_title) strcat(string,work_title); + if (work_date) strcat(string,work_date); + if (work_description) strcat(string,work_description); + if (work_creator) strcat(string,work_creator); + if (work_owner) strcat(string,work_owner); + if (work_source) strcat(string,work_source); + if (work_license) strcat(string,work_license); + + strcat(string,work_end); + if (license_head) { + strcat(string,license_head); + strcat(string,rdf->license->license_rdf); + strcat(string,license_end); + } + strcat(string,rdf_end); + + return string; +} +*/ + + +const gchar *RDFImpl::getReprText( Inkscape::XML::Node const * repr, struct rdf_work_entity_t const & entity ) +{ + g_return_val_if_fail (repr != nullptr, NULL); + static gchar * bag = nullptr; + gchar * holder = nullptr; + + Inkscape::XML::Node const * temp = nullptr; + switch (entity.datatype) { + case RDF_CONTENT: + temp = repr->firstChild(); + if ( temp == nullptr ) return nullptr; + + return temp->content(); + + case RDF_AGENT: + temp = sp_repr_lookup_name ( repr, "cc:Agent", 1 ); + if ( temp == nullptr ) return nullptr; + + temp = sp_repr_lookup_name ( temp, "dc:title", 1 ); + if ( temp == nullptr ) return nullptr; + + temp = temp->firstChild(); + if ( temp == nullptr ) return nullptr; + + return temp->content(); + + case RDF_RESOURCE: + return repr->attribute("rdf:resource"); + + case RDF_XML: + return "xml goes here"; + + case RDF_BAG: + /* clear the static string. yucky. */ + if (bag) g_free(bag); + bag = nullptr; + + temp = sp_repr_lookup_name ( repr, "rdf:Bag", 1 ); + if ( temp == nullptr ) { + /* backwards compatible: read contents */ + temp = repr->firstChild(); + if ( temp == nullptr ) return nullptr; + + return temp->content(); + } + + for ( temp = temp->firstChild() ; + temp ; + temp = temp->next() ) { + if (!strcmp(temp->name(),"rdf:li") && + temp->firstChild()) { + const gchar * str = temp->firstChild()->content(); + if (bag) { + holder = bag; + bag = g_strconcat(holder, ", ", str, nullptr); + g_free(holder); + } + else { + bag = g_strdup(str); + } + } + } + return bag; + + default: + break; + } + return nullptr; +} + +unsigned int RDFImpl::setReprText( Inkscape::XML::Node * repr, + struct rdf_work_entity_t const & entity, + gchar const * text ) +{ + g_return_val_if_fail ( repr != nullptr, 0); + g_return_val_if_fail ( text != nullptr, 0); + gchar * str = nullptr; + gchar** strlist = nullptr; + int i; + + Inkscape::XML::Node * temp=nullptr; + Inkscape::XML::Node * parent=repr; + + Inkscape::XML::Document * xmldoc = parent->document(); + g_return_val_if_fail (xmldoc != nullptr, FALSE); + + // set document's title element to the RDF title + if (!strcmp(entity.name, "title")) { + SPDocument *doc = SP_ACTIVE_DOCUMENT; + if (doc && doc->getRoot()) { + doc->getRoot()->setTitle(text); + } + } + + switch (entity.datatype) { + case RDF_CONTENT: + temp = parent->firstChild(); + if ( temp == nullptr ) { + temp = xmldoc->createTextNode( text ); + g_return_val_if_fail (temp != nullptr, FALSE); + + parent->appendChild(temp); + Inkscape::GC::release(temp); + + return TRUE; + } + else { + temp->setContent(text); + return TRUE; + } + + case RDF_AGENT: + temp = sp_repr_lookup_name ( parent, "cc:Agent", 1 ); + if ( temp == nullptr ) { + temp = xmldoc->createElement ( "cc:Agent" ); + g_return_val_if_fail (temp != nullptr, FALSE); + + parent->appendChild(temp); + Inkscape::GC::release(temp); + } + parent = temp; + + temp = sp_repr_lookup_name ( parent, "dc:title", 1 ); + if ( temp == nullptr ) { + temp = xmldoc->createElement ( "dc:title" ); + g_return_val_if_fail (temp != nullptr, FALSE); + + parent->appendChild(temp); + Inkscape::GC::release(temp); + } + parent = temp; + + temp = parent->firstChild(); + if ( temp == nullptr ) { + temp = xmldoc->createTextNode( text ); + g_return_val_if_fail (temp != nullptr, FALSE); + + parent->appendChild(temp); + Inkscape::GC::release(temp); + + return TRUE; + } + else { + temp->setContent(text); + return TRUE; + } + + case RDF_RESOURCE: + parent->setAttribute("rdf:resource", text ); + return true; + + case RDF_XML: + return 1; + + case RDF_BAG: + /* find/create the rdf:Bag item */ + temp = sp_repr_lookup_name ( parent, "rdf:Bag", 1 ); + if ( temp == nullptr ) { + /* backward compatibility: drop the dc:subject contents */ + while ( (temp = parent->firstChild()) ) { + parent->removeChild(temp); + } + + temp = xmldoc->createElement ( "rdf:Bag" ); + g_return_val_if_fail (temp != nullptr, FALSE); + + parent->appendChild(temp); + Inkscape::GC::release(temp); + } + parent = temp; + + /* toss all the old list items */ + while ( (temp = parent->firstChild()) ) { + parent->removeChild(temp); + } + + /* chop our list up on commas */ + strlist = g_strsplit( text, ",", 0); + + for (i = 0; (str = strlist[i]); i++) { + temp = xmldoc->createElement ( "rdf:li" ); + g_return_val_if_fail (temp != nullptr, 0); + + parent->appendChild(temp); + Inkscape::GC::release(temp); + + Inkscape::XML::Node * child = xmldoc->createTextNode( g_strstrip(str) ); + g_return_val_if_fail (child != nullptr, 0); + + temp->appendChild(child); + Inkscape::GC::release(child); + } + g_strfreev( strlist ); + + return 1; + + default: + break; + } + return 0; +} + +void RDFImpl::ensureParentIsMetadata( SPDocument *doc, Inkscape::XML::Node *node ) +{ + if ( !node ) { + g_critical("Null node passed to ensureParentIsMetadata()."); + } else if ( !node->parent() ) { + g_critical( "No parent node when verifying <metadata> placement." ); + } else { + Inkscape::XML::Node * currentParent = node->parent(); + if ( strcmp( currentParent->name(), XML_TAG_NAME_METADATA ) != 0 ) { + Inkscape::XML::Node * metadata = doc->getReprDoc()->createElement( XML_TAG_NAME_METADATA ); + if ( !metadata ) { + g_critical("Unable to create metadata element."); + } else { + // attach the metadata node + currentParent->appendChild( metadata ); + Inkscape::GC::release( metadata ); + + // move the node into it + Inkscape::GC::anchor( node ); + sp_repr_unparent( node ); + metadata->appendChild( node ); + Inkscape::GC::release( node ); + } + } + } +} + +Inkscape::XML::Node const *RDFImpl::getRdfRootRepr( SPDocument const * doc ) +{ + Inkscape::XML::Node const *rdf = nullptr; + if ( !doc ) { + g_critical("Null doc passed to getRdfRootRepr()"); + } else if ( !doc->getReprDoc() ) { + g_critical("XML doc is null."); + } else { + rdf = sp_repr_lookup_name( doc->getReprDoc(), XML_TAG_NAME_RDF ); + } + + return rdf; +} + +Inkscape::XML::Node *RDFImpl::ensureRdfRootRepr( SPDocument * doc ) +{ + Inkscape::XML::Node *rdf = nullptr; + if ( !doc ) { + g_critical("Null doc passed to ensureRdfRootRepr()"); + } else if ( !doc->getReprDoc() ) { + g_critical("XML doc is null."); + } else { + rdf = sp_repr_lookup_name( doc->getReprDoc(), XML_TAG_NAME_RDF ); + if ( !rdf ) { + Inkscape::XML::Node * svg = sp_repr_lookup_name( doc->getReprRoot(), XML_TAG_NAME_SVG ); + if ( !svg ) { + g_critical("Unable to locate svg element."); + } else { + Inkscape::XML::Node * parent = sp_repr_lookup_name( svg, XML_TAG_NAME_METADATA, 1); + if ( parent == nullptr ) { + parent = doc->getReprDoc()->createElement( XML_TAG_NAME_METADATA ); + if ( !parent ) { + g_critical("Unable to create metadata element"); + } else { + svg->appendChild(parent); + Inkscape::GC::release(parent); + } + } + + if ( parent && !parent->document() ) { + g_critical("Parent has no document"); + } else if ( parent ) { + rdf = parent->document()->createElement( XML_TAG_NAME_RDF ); + if ( !rdf ) { + g_critical("Unable to create root RDF element."); + } else { + parent->appendChild(rdf); + Inkscape::GC::release(rdf); + } + } + } + } + } + + if ( rdf ) { + ensureParentIsMetadata( doc, rdf ); + } + + return rdf; +} + +Inkscape::XML::Node const *RDFImpl::getXmlRepr( SPDocument const * doc, gchar const * name ) +{ + Inkscape::XML::Node const * xml = nullptr; + if ( !doc ) { + g_critical("Null doc passed to getXmlRepr()"); + } else if ( !doc->getReprDoc() ) { + g_critical("XML doc is null."); + } else if (!name) { + g_critical("Null name passed to getXmlRepr()"); + } else { + Inkscape::XML::Node const * rdf = getRdfRootRepr( doc ); + if ( rdf ) { + xml = sp_repr_lookup_name( rdf, name ); + } + } + return xml; +} + +Inkscape::XML::Node *RDFImpl::getXmlRepr( SPDocument * doc, gchar const * name ) +{ + Inkscape::XML::Node const *xml = getXmlRepr( const_cast<SPDocument const *>(doc), name ); + + return const_cast<Inkscape::XML::Node *>(xml); +} + +Inkscape::XML::Node *RDFImpl::ensureXmlRepr( SPDocument * doc, gchar const * name ) +{ + Inkscape::XML::Node * xml = nullptr; + if ( !doc ) { + g_critical("Null doc passed to ensureXmlRepr()"); + } else if ( !doc->getReprDoc() ) { + g_critical("XML doc is null."); + } else if (!name) { + g_critical("Null name passed to ensureXmlRepr()"); + } else { + Inkscape::XML::Node * rdf = ensureRdfRootRepr( doc ); + if ( rdf ) { + xml = sp_repr_lookup_name( rdf, name ); + if ( !xml ) { + xml = doc->getReprDoc()->createElement( name ); + if ( !xml ) { + g_critical("Unable to create xml element <%s>.", name); + } else { + xml->setAttribute("rdf:about", "" ); + + rdf->appendChild(xml); + Inkscape::GC::release(xml); + } + } + } + } + return xml; +} + +Inkscape::XML::Node const *RDFImpl::getWorkRepr( SPDocument const * doc, gchar const * name ) +{ + Inkscape::XML::Node const * item = nullptr; + if ( !doc ) { + g_critical("Null doc passed to getWorkRepr()"); + } else if ( !doc->getReprDoc() ) { + g_critical("XML doc is null."); + } else if (!name) { + g_critical("Null name passed to getWorkRepr()"); + } else { + Inkscape::XML::Node const* work = getXmlRepr( doc, XML_TAG_NAME_WORK ); + if ( work ) { + item = sp_repr_lookup_name( work, name, 1 ); + } + } + return item; +} + +Inkscape::XML::Node *RDFImpl::ensureWorkRepr( SPDocument * doc, gchar const * name ) +{ + Inkscape::XML::Node * item = nullptr; + if ( !doc ) { + g_critical("Null doc passed to ensureWorkRepr()"); + } else if ( !doc->getReprDoc() ) { + g_critical("XML doc is null."); + } else if (!name) { + g_critical("Null name passed to ensureWorkRepr()"); + } else { + Inkscape::XML::Node * work = ensureXmlRepr( doc, XML_TAG_NAME_WORK ); + if ( work ) { + item = sp_repr_lookup_name( work, name, 1 ); + if ( !item ) { + //printf("missing XML '%s'\n",name); + item = doc->getReprDoc()->createElement( name ); + if ( !item ) { + g_critical("Unable to create xml element <%s>", name); + } else { + work->appendChild(item); + Inkscape::GC::release(item); + } + } + } + } + return item; +} + + +// Public API: +const gchar *rdf_get_work_entity(SPDocument const * doc, struct rdf_work_entity_t * entity) +{ + const gchar *result = nullptr; + if ( !doc ) { + g_critical("Null doc passed to rdf_get_work_entity()"); + } else if ( entity ) { + //g_message("want '%s'\n",entity->title); + + result = RDFImpl::getWorkEntity( doc, *entity ); + + //g_message("found '%s' == '%s'\n", entity->title, result ); + } + return result; +} + +const gchar *RDFImpl::getWorkEntity(SPDocument const * doc, struct rdf_work_entity_t & entity) +{ + gchar const *result = nullptr; + + Inkscape::XML::Node const * item = getWorkRepr( doc, entity.tag ); + if ( item ) { + result = getReprText( item, entity ); + // TODO note that this is the location that used to set the title if needed. Ensure code it not required. + } + + return result; +} + +// Public API: +unsigned int rdf_set_work_entity(SPDocument * doc, struct rdf_work_entity_t * entity, + const gchar * text) +{ + unsigned int result = 0; + if ( !doc ) { + g_critical("Null doc passed to rdf_set_work_entity()"); + } else if ( entity ) { + result = RDFImpl::setWorkEntity( doc, *entity, text ); + } + + return result; +} + +unsigned int RDFImpl::setWorkEntity(SPDocument * doc, struct rdf_work_entity_t & entity, const gchar * text) +{ + int result = 0; + if (!text || !text[0]) { + auto *item = const_cast<Inkscape::XML::Node *>(getWorkRepr(doc, entity.tag)); + if (item) { + sp_repr_unparent(item); + if (!strcmp(entity.name, "title")) { + doc->getRoot()->setTitle(nullptr); + } + } + return true; + } + + /* + printf("changing '%s' (%s) to '%s'\n", + entity->title, + entity->tag, + text); + */ + + Inkscape::XML::Node * item = ensureWorkRepr( doc, entity.tag ); + if ( !item ) { + g_critical("Unable to get work element"); + } else { + result = setReprText( item, entity, text ); + } + return result; +} + + +#undef DEBUG_MATCH + +static bool +rdf_match_license(Inkscape::XML::Node const *repr, struct rdf_license_t const *license) +{ + g_assert ( repr != nullptr ); + g_assert ( license != nullptr ); + + bool result=TRUE; +#ifdef DEBUG_MATCH + printf("checking against '%s'\n",license->name); +#endif + + int count = 0; + for (struct rdf_double_t const *details = license->details; + details->name; details++ ) { + count++; + } + bool * matched = (bool*)calloc(count,sizeof(bool)); + + for (Inkscape::XML::Node const *current = repr->firstChild(); + current; + current = current->next() ) { + + gchar const * attr = current->attribute("rdf:resource"); + if ( attr == nullptr ) continue; + +#ifdef DEBUG_MATCH + printf("\texamining '%s' => '%s'\n", current->name(), attr); +#endif + + bool found_match=FALSE; + for (int i=0; i<count; i++) { + // skip already matched items + if (matched[i]) continue; + +#ifdef DEBUG_MATCH + printf("\t\t'%s' vs '%s'\n", current->name(), license->details[i].name); + printf("\t\t'%s' vs '%s'\n", attr, license->details[i].resource); +#endif + + if (!strcmp( current->name(), + license->details[i].name ) && + !strcmp( attr, + license->details[i].resource )) { + matched[i]=TRUE; + found_match=TRUE; +#ifdef DEBUG_MATCH + printf("\t\tgood!\n"); +#endif + break; + } + } + if (!found_match) { + // if we checked each known item of the license + // and didn't find it, we must abort + result=FALSE; +#ifdef DEBUG_MATCH + printf("\t\tno '%s' element matched XML (bong)!\n",license->name); +#endif + break; + } + } +#ifdef DEBUG_MATCH + if (result) printf("\t\tall XML found matching elements!\n"); +#endif + for (int i=0; result && i<count; i++) { + // scan looking for an unmatched item + if (matched[i]==0) { + result=FALSE; +#ifdef DEBUG_MATCH + printf("\t\tnot all '%s' elements used to match (bong)!\n", license->name); +#endif + } + } + +#ifdef DEBUG_MATCH + printf("\t\tall '%s' elements used to match!\n",license->name); +#endif + + free(matched); + +#ifdef DEBUG_MATCH + if (result) printf("matched '%s'\n",license->name); +#endif + return result; +} + +// Public API: +struct rdf_license_t *rdf_get_license(SPDocument *document) +{ + return RDFImpl::getLicense(document); +} + +struct rdf_license_t *RDFImpl::getLicense(SPDocument *document) +{ + // Base license lookup on the URI of cc:license rather than the license + // properties, per instructions from the ccREL gurus. + // (Fixes https://bugs.launchpad.net/inkscape/+bug/372427) + + struct rdf_work_entity_t *entity = rdf_find_entity("license_uri"); + if (entity == nullptr) { + g_critical("Can't find internal entity structure for 'license_uri'"); + return nullptr; + } + + const gchar *uri = getWorkEntity(document, *entity); + struct rdf_license_t * license_by_uri = nullptr; + + if (uri != nullptr) { + for (struct rdf_license_t * license = rdf_licenses; license->name; license++) { + if (g_strcmp0(uri, license->uri) == 0) { + license_by_uri = license; + break; + } + } + } + + // To improve backward compatibility, the old license matching code is + // kept as fallback and to warn about and fix discrepancies. + + // TODO: would it be better to do this code on document load? Is + // sp_metadata_build() then the right place to put the call to sort out + // any RDF mess? + + struct rdf_license_t * license_by_properties = nullptr; + + Inkscape::XML::Node const *repr = getXmlRepr( document, XML_TAG_NAME_LICENSE ); + if (repr) { + for ( struct rdf_license_t * license = rdf_licenses; license->name; license++ ) { + if ( rdf_match_license( repr, license ) ) { + license_by_properties = license; + break; + } + } + } + + if (license_by_uri != nullptr && license_by_properties != nullptr) { + // Both property and structure, use property + if (license_by_uri != license_by_properties) { + // TODO: this should be a user-visible warning, but how? + g_warning("Mismatch between %s and %s metadata:\n" + "%s value URI: %s (using this one!)\n" + "%s derived URI: %s", + XML_TAG_NAME_LICENSE_PROP, + XML_TAG_NAME_LICENSE, + XML_TAG_NAME_LICENSE_PROP, + license_by_uri->uri, + XML_TAG_NAME_LICENSE, + license_by_properties->uri); + } + + // Reset license structure to match so the document is consistent + // (and this will also silence the warning above on repeated calls). + setLicense(document, license_by_uri); + + return license_by_uri; + } + else if (license_by_uri != nullptr) { + // Only cc:license property, set structure for backward compatibility + setLicense(document, license_by_uri); + + return license_by_uri; + } + else if (license_by_properties != nullptr) { + // Only cc:License structure + // TODO: this could be a user-visible warning too + g_warning("No %s metadata found, derived license URI from %s: %s", + XML_TAG_NAME_LICENSE_PROP, XML_TAG_NAME_LICENSE, + license_by_properties->uri); + + // Set license property to match + setWorkEntity(document, *entity, license_by_properties->uri); + + return license_by_properties; + } + + // No license info at all + return nullptr; +} + +// Public API: +void rdf_set_license(SPDocument * doc, struct rdf_license_t const * license) +{ + RDFImpl::setLicense( doc, license ); +} + +void RDFImpl::setLicense(SPDocument * doc, struct rdf_license_t const * license) +{ + // When basing license check on only the license URI (see fix for + // https://bugs.launchpad.net/inkscape/+bug/372427 above) we should + // really drop this license section, but keep writing it for a while for + // compatibility with older versions. + + // drop old license section + Inkscape::XML::Node * repr = getXmlRepr( doc, XML_TAG_NAME_LICENSE ); + if (repr) { + sp_repr_unparent(repr); + } + + if ( !license ) { + // All done + } else if ( !doc->getReprDoc() ) { + g_critical("XML doc is null."); + } else { + // build new license section + repr = ensureXmlRepr( doc, XML_TAG_NAME_LICENSE ); + g_assert( repr ); + + repr->setAttribute("rdf:about", license->uri ); + + for (struct rdf_double_t const * detail = license->details; detail->name; detail++) { + Inkscape::XML::Node * child = doc->getReprDoc()->createElement( detail->name ); + g_assert ( child != nullptr ); + + child->setAttribute("rdf:resource", detail->resource ); + repr->appendChild(child); + Inkscape::GC::release(child); + } + } +} + +// Public API: +void rdf_set_defaults( SPDocument * doc ) +{ + RDFImpl::setDefaults( doc ); + +} + +void RDFImpl::setDefaults( SPDocument * doc ) +{ + g_assert( doc != nullptr ); + + // Used to add hard coded defaults here +} + +/* + * Add the metadata stored in the users preferences to the document if + * a) the preference 'Input/Output->Add default metadata to new documents' is true, and + * b) there is no metadata already in the file (such as from a template) + */ +void rdf_add_from_preferences(SPDocument *doc) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if (!prefs->getBool("/metadata/addToNewFile")) { + return; + } + + // If there is already some metadata in the doc (from a template) don't add default metadata + for (struct rdf_work_entity_t *entity = rdf_work_entities; entity && entity->name; entity++) { + if ( entity->editable == RDF_EDIT_GENERIC && + rdf_get_work_entity (doc, entity)) { + return; + } + } + + // Put the metadata from user preferences into the doc + for (struct rdf_work_entity_t *entity = rdf_work_entities; entity && entity->name; entity++) { + if ( entity->editable == RDF_EDIT_GENERIC ) { + Glib::ustring text = prefs->getString(PREFS_METADATA + Glib::ustring(entity->name)); + if (text.length() > 0) { + rdf_set_work_entity (doc, entity, text.c_str()); + } + } + } +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : |