Extract and show license key

List of available licenses is pre-extracted from UploadWizard
MediaWiki extension. This should eventually be switched to use
some live query to the site configuration.

The license display is more or less localizable, for known templates.
Unrecognized templates specified as parameters to {{self}} will be
recognized (unlocalized) but others may not be.

Change-Id: I9df5fe807798a191a3bb0a45464760c75f19e366
This commit is contained in:
Brion Vibber 2013-09-24 14:36:54 -07:00
parent dde64dc4d2
commit 4c45c88ade
11 changed files with 440 additions and 4 deletions

View file

@ -31,6 +31,14 @@
android:text="Description of the media goes here. This can potentially be fairly long, and will need to wrap across multiple lines. We hope it looks nice though." android:text="Description of the media goes here. This can potentially be fairly long, and will need to wrap across multiple lines. We hope it looks nice though."
android:id="@+id/mediaDetailDesc" android:id="@+id/mediaDetailDesc"
android:layout_gravity="left|start"/> android:layout_gravity="left|start"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="License link"
android:id="@+id/mediaDetailLicense"
android:layout_gravity="left|start"
android:textColor="@android:color/white"
android:textSize="18sp" /> <!-- 18sp == MediumText -->
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View file

@ -93,9 +93,42 @@
<string name="menu_download">Download</string> <string name="menu_download">Download</string>
<string name="preference_license">License</string> <string name="preference_license">License</string>
<!-- These three are semi-legacy entries, and should be changed in future -->
<string name="license_name_cc_by_sa">CC\u00A0Attribution-ShareAlike\u00A03.0</string> <string name="license_name_cc_by_sa">CC\u00A0Attribution-ShareAlike\u00A03.0</string>
<string name="license_name_cc_by">CC\u00A0Attribution\u00A03.0</string> <string name="license_name_cc_by">CC\u00A0Attribution\u00A03.0</string>
<string name="license_name_cc0">CC0</string> <string name="license_name_cc0">CC0</string>
<!-- Licenses from the default UploadWizard selection list -->
<string name="license_name_cc_by_sa_3_0">CC BY-SA 3.0</string>
<string name="license_name_cc_by_sa_3_0_at">CC BY-SA 3.0 (Austria)</string>
<string name="license_name_cc_by_sa_3_0_de">CC BY-SA 3.0 (Germany)</string>
<string name="license_name_cc_by_sa_3_0_ee">CC BY-SA 3.0 (Estonia)</string>
<string name="license_name_cc_by_sa_3_0_es">CC BY-SA 3.0 (Spain)</string>
<string name="license_name_cc_by_sa_3_0_hr">CC BY-SA 3.0 (Croatia)</string>
<string name="license_name_cc_by_sa_3_0_lu">CC BY-SA 3.0 (Luxembourg)</string>
<string name="license_name_cc_by_sa_3_0_nl">CC BY-SA 3.0 (Netherlands)</string>
<string name="license_name_cc_by_sa_3_0_no">CC BY-SA 3.0 (Norway)</string>
<string name="license_name_cc_by_sa_3_0_pl">CC BY-SA 3.0 (Poland)</string>
<string name="license_name_cc_by_sa_3_0_ro">CC BY-SA 3.0 (Romania)</string>
<string name="license_name_cc_by_3_0">CC BY 3.0</string>
<string name="license_name_cc_zero">CC Zero</string>
<string name="license_name_own_pd">own-pd</string>
<string name="license_name_cc_by_sa_2_5">CC BY-SA 2.5</string>
<string name="license_name_cc_by_2_5">CC BY 2.5</string>
<string name="license_name_cc_by_sa_2_0">CC BY-SA 2.0</string>
<string name="license_name_cc_by_2_0">CC BY-SA 2.0</string>
<string name="license_name_cc_2_0">CC BY 2.0</string>
<string name="license_name_fal">Free Art License</string>
<string name="license_name_pd_old_100">Public domain (author died over 100 years ago)</string>
<string name="license_name_pd_old">Public domain (copyright expired)</string>
<string name="license_name_pd_art">Public domain (art)</string>
<string name="license_name_pd_us">Public domain (US)</string>
<string name="license_name_pd_usgov">Public domain (US government)</string>
<string name="license_name_pd_usgov_nasa">Public domain (NASA)</string>
<string name="license_name_pd_ineligible">Public domain (ineligible for copyright)</string>
<string name="license_name_attribution">Attribution</string>
<string name="license_name_gfdl">GNU Free Documentation License</string>
<string name="welcome_wikipedia_text">Contribute your images. Help Wikipedia articles come to life!</string> <string name="welcome_wikipedia_text">Contribute your images. Help Wikipedia articles come to life!</string>
<string name="welcome_wikipedia_subtext">Images on Wikipedia come from Wikimedia Commons.</string> <string name="welcome_wikipedia_subtext">Images on Wikipedia come from Wikimedia Commons.</string>
<string name="welcome_copyright_text">Your images help educate people around the world.</string> <string name="welcome_copyright_text">Your images help educate people around the world.</string>

View file

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<licenses xmlns="https://www.mediawiki.org/wiki/Extension:UploadWizard/xmlns/licenses">
<license id="cc-by-sa-3.0" template="cc-by-sa-3.0" url="https://creativecommons.org/licenses/by-sa/3.0/deed.$lang"/>
<license id="cc-by-sa-3.0-at" template="cc-by-sa-3.0-at" url="https://creativecommons.org/licenses/by-sa/3.0/at/deed.$lang"/>
<license id="cc-by-sa-3.0-de" template="cc-by-sa-3.0-de" url="https://creativecommons.org/licenses/by-sa/3.0/de/deed.$lang"/>
<license id="cc-by-sa-3.0-ee" template="cc-by-sa-3.0-ee" url="https://creativecommons.org/licenses/by-sa/3.0/ee/deed.$lang"/>
<license id="cc-by-sa-3.0-es" template="cc-by-sa-3.0-es" url="https://creativecommons.org/licenses/by-sa/3.0/es/deed.$lang"/>
<license id="cc-by-sa-3.0-hr" template="cc-by-sa-3.0-hr" url="https://creativecommons.org/licenses/by-sa/3.0/hr/deed.$lang"/>
<license id="cc-by-sa-3.0-lu" template="cc-by-sa-3.0-lu" url="https://creativecommons.org/licenses/by-sa/3.0/lu/deed.$lang"/>
<license id="cc-by-sa-3.0-nl" template="cc-by-sa-3.0-nl" url="https://creativecommons.org/licenses/by-sa/3.0/nl/deed.$lang"/>
<license id="cc-by-sa-3.0-no" template="cc-by-sa-3.0-no" url="https://creativecommons.org/licenses/by-sa/3.0/no/deed.$lang"/>
<license id="cc-by-sa-3.0-pl" template="cc-by-sa-3.0-pl" url="https://creativecommons.org/licenses/by-sa/3.0/pl/deed.$lang"/>
<license id="cc-by-sa-3.0-ro" template="cc-by-sa-3.0-ro" url="https://creativecommons.org/licenses/by-sa/3.0/ro/deed.$lang"/>
<license id="cc-by-3.0" template="cc-by-3.0" url="https://creativecommons.org/licenses/by/3.0/deed.$lang"/>
<license id="cc-zero" template="cc-zero" url="https://creativecommons.org/publicdomain/zero/1.0/deed.$lang"/>
<license id="own-pd" template="cc-zero"/>
<license id="cc-by-sa-2.5" template="cc-by-sa-2.5" url="https://creativecommons.org/licenses/by-sa/2.5/deed.$lang"/>
<license id="cc-by-2.5" template="cc-by-2.5" url="https://creativecommons.org/licenses/by/2.5/deed.$lang"/>
<license id="cc-by-sa-2.0" template="cc-by-sa-2.0" url="https://creativecommons.org/licenses/by-sa/2.0/deed.$lang"/>
<license id="cc-by-2.0" template="cc-by-2.0" url="https://creativecommons.org/licenses/by/2.0/deed.$lang"/>
<license id="fal" template="FAL"/>
<license id="pd-old-100" template="PD-old-100"/>
<license id="pd-old" template="PD-old"/>
<license id="pd-art" template="PD-Art"/>
<license id="pd-us" template="PD-US"/>
<license id="pd-usgov" template="PD-USGov"/>
<license id="pd-usgov-nasa" template="PD-USGov-NASA"/>
<license id="pd-ineligible" template="pd-ineligible"/>
<license id="attribution" template="attribution"/>
<license id="gfdl" template="GFDL"/>
</licenses>

View file

@ -0,0 +1,46 @@
package org.wikimedia.commons;
public class License {
String key;
String template;
String url;
String name;
public License(String key, String template, String url, String name) {
if (key == null) {
throw new RuntimeException("License.key must not be null");
}
if (template == null) {
throw new RuntimeException("License.template must not be null");
}
this.key = key;
this.template = template;
this.url = url;
this.name = name;
}
public String getKey() {
return key;
}
public String getTemplate() {
return template;
}
public String getName() {
if (name == null) {
// hack
return getKey();
} else {
return name;
}
}
public String getUrl(String language) {
if (url == null) {
return null;
} else {
return url.replace("$lang", language);
}
}
}

View file

@ -0,0 +1,75 @@
package org.wikimedia.commons;
import android.app.Activity;
import android.content.res.Resources;
import android.util.Log;
import org.xmlpull.v1.XmlPullParser;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class LicenseList {
Map<String, License> licenses = new HashMap<String, License>();
Resources res;
private static String XMLNS_LICENSE = "https://www.mediawiki.org/wiki/Extension:UploadWizard/xmlns/licenses";
public LicenseList(Activity activity) {
res = activity.getResources();
XmlPullParser parser = res.getXml(R.xml.wikimedia_licenses);
while (Utils.xmlFastForward(parser, XMLNS_LICENSE, "license")) {
String id = parser.getAttributeValue(null, "id");
String template = parser.getAttributeValue(null, "template");
String url = parser.getAttributeValue(null, "url");
String name = nameForTemplate(template);
License license = new License(id, template, url, name);
licenses.put(id, license);
}
}
public Set<String> keySet() {
return licenses.keySet();
}
public Collection<License> values() {
return licenses.values();
}
public License get(String key) {
return licenses.get(key);
}
public License licenseForTemplate(String template) {
String ucTemplate = Utils.capitalize(template);
for (License license : values()) {
if (ucTemplate.equals(Utils.capitalize(license.getTemplate()))) {
return license;
}
}
return null;
}
public String nameIdForTemplate(String template) {
// hack :D
// cc-by-sa-3.0 -> cc_by_sa_3_0
return "license_name_" + template.toLowerCase().replace("-", "_").replace(".", "_");
}
private int stringIdByName(String stringId) {
return res.getIdentifier("org.wikimedia.commons:string/" + stringId, null, null);
}
public String nameForTemplate(String template) {
Log.d("Commons", "LicenseList.nameForTemplate: template: " + template);
String stringId = nameIdForTemplate(template);
Log.d("Commons", "LicenseList.nameForTemplate: stringId: " + stringId);
int nameId = stringIdByName(stringId);
Log.d("Commons", "LicenseList.nameForTemplate: nameId: " + nameId);
String name = res.getString(nameId);
Log.d("Commons", "LicenseList.nameForTemplate: name: " + name);
return name;
}
}

View file

@ -33,16 +33,19 @@ public class MediaDataExtractor {
private Map<String, String> descriptions; private Map<String, String> descriptions;
private String author; private String author;
private Date date; private Date date;
private String license;
private LicenseList licenseList;
/** /**
* @param filename of the target media object, should include 'File:' prefix * @param filename of the target media object, should include 'File:' prefix
*/ */
public MediaDataExtractor(String filename) { public MediaDataExtractor(String filename, LicenseList licenseList) {
this.filename = filename; this.filename = filename;
categories = new ArrayList<String>(); categories = new ArrayList<String>();
descriptions = new HashMap<String, String>(); descriptions = new HashMap<String, String>();
fetched = false; fetched = false;
processed = false; processed = false;
this.licenseList = licenseList;
} }
/** /**
@ -114,7 +117,41 @@ public class MediaDataExtractor {
descriptions = getMultilingualText(descriptionNode); descriptions = getMultilingualText(descriptionNode);
Node authorNode = findTemplateParameter(templateNode, "author"); Node authorNode = findTemplateParameter(templateNode, "author");
author = Utils.getStringFromDOM(authorNode); author = getFlatText(authorNode);
}
/*
Pull up the license data list...
look for the templates in two ways:
* look for 'self' template and check its first parameter
* if none, look for any of the known templates
*/
Log.d("Commons", "MediaDataExtractor searching for license");
Node selfLicenseNode = findTemplate(doc.getDocumentElement(), "self");
if (selfLicenseNode != null) {
Node firstNode = findTemplateParameter(selfLicenseNode, 1);
String licenseTemplate = getFlatText(firstNode);
License license = licenseList.licenseForTemplate(licenseTemplate);
if (license == null) {
Log.d("Commons", "MediaDataExtractor found no matching license for self parameter: " + licenseTemplate + "; faking it");
this.license = licenseTemplate; // hack hack! For non-selectable licenses that are still in the system.
} else {
// fixme: record the self-ness in here too... sigh
// all this needs better server-side metadata
this.license = license.getKey();
Log.d("Commons", "MediaDataExtractor found self-license " + this.license);
}
} else {
for (License license : licenseList.values()) {
String templateName = license.getTemplate();
Node template = findTemplate(doc.getDocumentElement(), templateName);
if (template != null) {
// Found!
this.license = license.getKey();
Log.d("Commons", "MediaDataExtractor found non-self license " + this.license);
break;
}
}
} }
} }
@ -201,6 +238,10 @@ public class MediaDataExtractor {
throw new IOException("No matching template parameter node found."); throw new IOException("No matching template parameter node found.");
} }
private String getFlatText(Node parentNode) throws IOException {
return parentNode.getTextContent();
}
// Extract a dictionary of multilingual texts from a subset of the parse tree. // Extract a dictionary of multilingual texts from a subset of the parse tree.
// Texts are wrapped in things like {{en|foo} or {{en|1=foo bar}}. // Texts are wrapped in things like {{en|foo} or {{en|1=foo bar}}.
// Text outside those wrappers is stuffed into a 'default' faux language key if present. // Text outside those wrappers is stuffed into a 'default' faux language key if present.
@ -246,6 +287,9 @@ public class MediaDataExtractor {
media.setCategories(categories); media.setCategories(categories);
media.setDescriptions(descriptions); media.setDescriptions(descriptions);
if (license != null) {
media.setLicense(license);
}
// add author, date, etc fields // add author, date, etc fields
} }

View file

@ -10,6 +10,8 @@ import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.codec.net.URLCodec; import org.apache.commons.codec.net.URLCodec;
import org.w3c.dom.Node; import org.w3c.dom.Node;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import javax.xml.transform.*; import javax.xml.transform.*;
import java.io.*; import java.io.*;
@ -194,4 +196,33 @@ public class Utils {
String uriStr = CommonsApplication.HOME_URL + urlEncode(underscored); String uriStr = CommonsApplication.HOME_URL + urlEncode(underscored);
return Uri.parse(uriStr); return Uri.parse(uriStr);
} }
/**
* Fast-forward an XmlPullParser to the next instance of the given element
* in the input stream (namespaced).
*
* @param parser
* @param namespace
* @param element
* @return true on match, false on failure
*/
public static boolean xmlFastForward(XmlPullParser parser, String namespace, String element) {
try {
while (parser.next() != XmlPullParser.END_DOCUMENT) {
if (parser.getEventType() == XmlPullParser.START_TAG &&
parser.getNamespace().equals(namespace) &&
parser.getName().equals(element)) {
// We found it!
return true;
}
}
return false;
} catch (XmlPullParserException e) {
e.printStackTrace();
return false;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
} }

View file

@ -59,6 +59,7 @@ public class MediaDetailFragment extends SherlockFragment {
private TextView title; private TextView title;
private TextView desc; private TextView desc;
private TextView license;
private ListView listView; private ListView listView;
private ArrayList<String> categoryNames; private ArrayList<String> categoryNames;
private boolean categoriesLoaded = false; private boolean categoriesLoaded = false;
@ -133,6 +134,7 @@ public class MediaDetailFragment extends SherlockFragment {
spacer = (MediaDetailSpacer) detailView.findViewById(R.id.mediaDetailSpacer); spacer = (MediaDetailSpacer) detailView.findViewById(R.id.mediaDetailSpacer);
title = (TextView) detailView.findViewById(R.id.mediaDetailTitle); title = (TextView) detailView.findViewById(R.id.mediaDetailTitle);
desc = (TextView) detailView.findViewById(R.id.mediaDetailDesc); desc = (TextView) detailView.findViewById(R.id.mediaDetailDesc);
license = (TextView) detailView.findViewById(R.id.mediaDetailLicense);
// Enable or disable editing on the title // Enable or disable editing on the title
/* /*
@ -160,10 +162,12 @@ public class MediaDetailFragment extends SherlockFragment {
// FIXME: cache this data // FIXME: cache this data
detailFetchTask = new AsyncTask<Void, Void, Boolean>() { detailFetchTask = new AsyncTask<Void, Void, Boolean>() {
private MediaDataExtractor extractor; private MediaDataExtractor extractor;
private LicenseList licenseList;
@Override @Override
protected void onPreExecute() { protected void onPreExecute() {
extractor = new MediaDataExtractor(media.getFilename()); licenseList = new LicenseList(getActivity());
extractor = new MediaDataExtractor(media.getFilename(), licenseList);
} }
@Override @Override
@ -187,6 +191,16 @@ public class MediaDetailFragment extends SherlockFragment {
// Fill some fields // Fill some fields
desc.setText(media.getDescription("en")); desc.setText(media.getDescription("en"));
String licenseKey = media.getLicense();
License licenseObj = licenseList.get(licenseKey);
if (licenseObj == null) {
license.setText(licenseKey);
} else {
license.setText(licenseObj.getName());
}
Log.d("Commons", "Media license is: " + media.getLicense());
categoryNames.removeAll(categoryNames); categoryNames.removeAll(categoryNames);
categoryNames.addAll(media.getCategories()); categoryNames.addAll(media.getCategories());
@ -231,7 +245,8 @@ public class MediaDetailFragment extends SherlockFragment {
} }
title.setText(media.getDisplayTitle()); title.setText(media.getDisplayTitle());
desc.setText(""); desc.setText(""); // fill in from network...
license.setText(""); // fill in from network...
/* /*
title.addTextChangedListener(new TextWatcher() { title.addTextChangedListener(new TextWatcher() {

View file

@ -0,0 +1,14 @@
.FAKE : build update clean install
build : ../commons/res/xml/wikimedia_licenses.xml
../commons/res/xml/wikimedia_licenses.xml : licenses.php mediawiki-extensions-UploadWizard
php licenses.php > ../commons/res/xml/wikimedia_licenses.xml
mediawiki-extensions-UploadWizard : update
update :
if [ -d mediawiki-extensions-UploadWizard ]; then (cd mediawiki-extensions-UploadWizard && git pull origin master); else git clone https://gerrit.wikimedia.org/r/mediawiki/extensions/UploadWizard mediawiki-extensions-UploadWizard; fi
clean :
rm -rf mediawiki-extensions-UploadWizard

View file

@ -0,0 +1,68 @@
<?php
// Stubs for enough of the MediaWiki environment to run UploadWizard.config.php
global $wgFileExtensions, $wgServer, $wgScriptPath, $wgAPIModules, $wgMaxUploadSize, $wgLang, $wgMemc, $wgUploadWizardConfig;
class FakeLang {
function getCode() {
return 'en';
}
}
$wgLang = new FakeLang();
function wfMemcKey() {
return 'fake-key';
}
class FakeMemc {
function get() {
return array( 'en' => 'English' );
}
}
$wgMemc = new FakeMemc();
class FakeMessage {
function plain() {
return 'stub-message-plain';
}
function parse() {
return 'stub-message-parsed';
}
}
function wfMessage() {
return new FakeMessage();
}
/**
* Converts shorthand byte notation to integer form
*
* @param $string String
* @return Integer
*/
function wfShorthandToInteger( $string = '' ) {
$string = trim( $string );
if ( $string === '' ) {
return -1;
}
$last = $string[strlen( $string ) - 1];
$val = intval( $string );
switch ( $last ) {
case 'g':
case 'G':
$val *= 1024;
// break intentionally missing
case 'm':
case 'M':
$val *= 1024;
// break intentionally missing
case 'k':
case 'K':
$val *= 1024;
}
return $val;
}
$wgAPIModules = array();

View file

@ -0,0 +1,71 @@
<?php
// Quick hack to extract default license list from UploadWizard configuration.
// In future, try to export this info via the API on wiki so we can pull dynamically.
//
// Brion Vibber <brion@pobox.com>
// 2013-09-30
require 'include-stubs.php';
$config = require "mediawiki-extensions-UploadWizard/UploadWizard.config.php";
require "mediawiki-extensions-UploadWizard/UploadWizard.i18n.php";
$licenseList = array();
foreach ( $config['licenses'] as $key => $license ) {
// Determine template -> license mappings
if ( isset( $license['templates'] ) ) {
$templates = $license['templates'];
} else {
$templates = array( $key );
}
if ( count( $templates ) < 1 ) {
throw new Exception("No templates for $key, this is wrong.");
}
if ( count( $templates ) > 1 ) {
//echo "Skipping multi-template license: $key\n";
continue;
}
$template = $templates[0];
if ( preg_match( '/^subst:/i', $template ) ) {
//echo "Skipping subst license: $key\n";
continue;
}
$msg = $messages['en'][$license['msg']];
$licenseInfo = array(
'desc' => $msg,
'template' => $template
);
if ( isset( $license['url'] ) ) {
$url = $license['url'];
if ( substr( $url, 0, 2 ) == '//' ) {
$url = 'https:' . $url;
}
if ( isset( $license['languageCodePrefix'] ) ) {
$url .= $license['languageCodePrefix'] . '$lang';
}
$licenseInfo['url'] = $url;
}
$licenseList[$key] = $licenseInfo;
}
//var_dump( $licenseList );
echo "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
echo "<licenses xmlns=\"https://www.mediawiki.org/wiki/Extension:UploadWizard/xmlns/licenses\">\n";
foreach( $licenseList as $key => $licenseInfo ) {
$encId = htmlspecialchars( $key );
echo " <license id=\"$encId\"";
$encTemplate = htmlspecialchars( $licenseInfo['template'] );
echo " template=\"$encTemplate\"";
if ( isset( $licenseInfo['url'] ) ) {
$encUrl = htmlspecialchars( $licenseInfo['url'] );
echo " url=\"$encUrl\"";
}
echo "/>\n";
}
echo "</licenses>\n";