// SPDX-License-Identifier: GPL-2.0-or-later /** @file * RDF manipulation functions. * * @todo move these to xml/ instead of dialogs/ */ /* Authors: * Kees Cook * Jon Phillips * Jon A. Cruz * * Copyright (C) 2004 Kees Cook * Copyright (C) 2006 Jon Phillips * * 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... title of work year description of work creator holder SVG Road Signs John Cliff Bag example: open clip art logo images logo clip art ocal logotype filetype */ // 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 , * 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="\ \ "; gchar *work_head="\ \ \ "; 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="\n"; gchar *work_end="\n"; gchar *rdf_end="\n"; if (rdf && rdf->work_title && rdf->work_title[0]) { work_title=g_markup_printf_escaped(" %s\n", rdf->work_title); overall+=strlen(work_title); } if (rdf && rdf->work_date && rdf->work_date[0]) { work_date=g_markup_printf_escaped(" %s\n", rdf->work_date); overall+=strlen(work_date); } if (rdf && rdf->work_description && rdf->work_description[0]) { work_description=g_markup_printf_escaped(" %s\n", rdf->work_description); overall+=strlen(work_description); } if (rdf && rdf->work_creator && rdf->work_creator[0]) { work_creator=g_markup_printf_escaped(" \ %s\ \n", rdf->work_creator); overall+=strlen(work_creator); } if (rdf && rdf->work_owner && rdf->work_owner[0]) { work_owner=g_markup_printf_escaped(" \ %s\ \n", rdf->work_owner); overall+=strlen(work_owner); } if (rdf && rdf->work_source && rdf->work_source[0]) { work_source=g_markup_printf_escaped(" \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(" \n", rdf->license->work_rdf); overall+=strlen(work_license); license_head=g_markup_printf_escaped("\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 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(doc), name ); return const_cast(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(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; iname(), 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 && iname); #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 :