МедиаВики:Gadget-wikidataInfoboxExport.js

Wikipedia — ирекле энциклопедия проектыннан ([http://tt.wikipedia.org.ttcysuttlart1999.aylandirow.tmf.org.ru/wiki/Gadget-wikidataInfoboxExport.js latin yazuında])

Искәрмә: Мөгаен, үзгәрүләрне күрер өчен сезгә үзгәртүләр ясаганнан соң браузерыгызның кэшын чистартырга туры килер.

  • Firefox / Safari: Shift төймәсенә басып торып, кораллар панеленда Яңартырга дигәненә яки Ctrl+F5 яисә Ctrl+R (Macта ⌘+R) дигәненә басыгыз
  • Google Chrome: Ctrl+Shift+R (Macта ⌘+Shift+R) басыгыз
  • Internet Explorer / Edge: Ctrl басып тотып, Яңартырга дигәненә басыгыз, яки Ctrl+F5 басыгыз
  • Opera: басыгыз Ctrl+F5.
/**
 * Быстрый экспорт информации из карточек в Викиданные.
 * Вызов окна экспорта по двойному клику.
 */
( function ( mw, $ ) {
	var wdeConfig = {
		version: '1.16.12',
		languages: [ 'en' ],
		references: {
			// взято из = русская Википедия
			P143: [ {
				snaktype: 'value',
				property: 'P143',
				datavalue: {
					type: 'wikibase-entityid',
					value: { id: 'Q206855' }
				}
			} ]
		},
		units: {
			Q531: { search: [ 'св(?:\\.|етовых)\\s*(?:лет|года?)' ] },
			Q4916: { search: [ '^€', '^EUR' ] },
			Q4917: { search: [ '^(?:-|US)?\\$', 'долларов(?:\\sСША)?', 'долл.' ] },
			Q7727: { search: [ 'минут' ] },
			Q8146: { search: [ 'иен' ] },
			Q11573: { search: [ 'метр(?:а|ов)' ] },
			Q25344: { search: [ '^CHF' ] },
			Q41044: { search: [ 'рублей' ] },
			Q81292: { search: [ 'акр(?:а|ов)' ] },
			Q21074767: { search: [ '(?:в[её]рст²|кв\.\s*в[её]рст)' ] }
		},
		reCirca: '(?:~|≈|ок(?:оло)?|прим(?:ерно)?)\\.?',
		reBCE: 'до\\s\+н\\.\\s\*э\\.?\\s\*$',
		reCE: 'н\\.\\s\*э\\.?\\s\*$',
		misLang: {
			ady: 'Q27776',
			ain: 'Q20968488',
			alt: 'Q1991779',
			atv: 'Q2640863',
			ckt: 'Q33170',
			enf: 'Q29942',
			evn: 'Q30004',
			inh: 'Q33509',
			izh: 'Q33559',
			jdt: 'Q56495',
			kjh: 'Q33575',
			krl: 'Q33557',
			kum: 'Q36209',
			orv: 'Q35228',
			otk: 'Q34988',
			pdt: 'Q1751432',
			sjd: 'Q33656',
			sjt: 'Q36656',
			sma: 'Q13293',
			vot: 'Q32858',
			yrk: 'Q36452'
		}
	};

	var api;
	var wdApi;

	var wdeBaseRevId;
	var wdePropertyIds = [ 'P2076', 'P2077' ]; // температура и давление для квалификаторов
	var wdeTypesMapping = {
		'commonsMedia': 'string',
		'external-id': 'string',
		'url': 'string',
		'wikibase-item': 'wikibase-entityid'
	};
	var wdeAlreadyExistingItems = {};
	var wdeWindowManager;
	var centuries = [ 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII',
		'VIII', 'IX', 'X', 'XI', 'XII', 'XIII', 'XIV', 'XV',
		'XVI', 'XVII', 'XVIII', 'XIX', 'XX', 'XXI', 'XXII' ];

	/**
	 * Сравнивает значение из карточки и в Викиданных
	 */
	var wdeCanExportValue = function ( $field, claims, callbackIfCan ) {
		if ( !claims || !( claims.length ) ) {
			// не можем экспортировать только если это крупное изображение из ru-wiki
			if ( !( $field.find( '.image img[src*="/wikipedia/' + wdeConfig.language + '/"]' ).width() >= 80 ) ) {
				callbackIfCan();
			}
			return;
		}

		switch ( claims[ 0 ].mainsnak.datatype ) {
			case 'quantity':
				for ( var i = 0; i < claims.length; i++ ) {
					var parsedTime = wdeCreateTimeSnak( ( $field.text().match( /\(([^)]*\d\d\d\d)[,)\s]/ ) || [] )[ 1 ] );
					if ( parsedTime && ( claims[ i ].qualifiers || {} ).P585 ) {
						var claimPrecision = claims[ i ].qualifiers.P585[ 0 ].datavalue.value.precision;
						if ( parsedTime.precision < claimPrecision ) {
							claims[ i ].qualifiers.P585[ 0 ].datavalue.value.precision = parsedTime.precision;
						} else if ( parsedTime.precision > claimPrecision ) { // FIXME: потом уточнить в wd дату
							parsedTime.precision = claimPrecision;
						}
						var p585 = parsedTime ? wdeFormatDataValue( {
							type: 'time',
							value: parsedTime
						} )[ 0 ].innerText : '';

						if ( wdeFormatDataValue( claims[ i ].qualifiers.P585[ 0 ].datavalue )[ 0 ].innerText !== p585 ) {
							claims[ i ].qualifiers.P585[ 0 ].datavalue.value.precision = claimPrecision;
							continue;
						}
					}
					return;
				}
				callbackIfCan( true );
				break;

			case 'wikibase-item':
				value = wdeParseItems( $field, $field, function ( values ) {
					var duplicates = [];
					for ( var i = 0; i < values.length; i++ ) {
						for ( var j = 0; j < claims.length; j++ ) {
							if ( values[ i ].wd.value.id === claims[ j ].mainsnak.datavalue.value.id ) {
								duplicates.push( values[ i ].wd.value.id );
							}
						}
					}
					if ( duplicates.length < values.length ) {
						if ( duplicates.length > 0 ) {
							var propertyId = claims[ 0 ].mainsnak.property;
							wdeAlreadyExistingItems[ propertyId ] = duplicates;
							if ( propertyId === 'P166' && values.length === claims.length )
								return;
						} 
						if ( claims.length > 0 ) {
							if ( claims[ 0 ].mainsnak.property === 'P19' || claims[ 0 ].mainsnak.property === 'P20' )
								return;
						}
						callbackIfCan( true );
					}
				} );
		}
		// По умолчанию, если есть claims, то экспортировать нельзя
	};

	/**
	 * Генерация ID утверждения
	 */
	var wdeClaimGuid = function ( entityId ) {
		var getRandomHex = function ( min, max ) {
			return ( Math.floor( Math.random() * ( max - min + 1 ) ) + min ).toString( 16 );
		};

		var template = 'xx-x-x-x-xxx';
		var guid = '';
		for ( var i = 0; i < template.length; i++ ) {
			if ( template.charAt( i ) === '-' ) {
				guid += '-';
				continue;
			}

			var hex;
			if ( i === 3 ) {
				hex = getRandomHex( 16384, 20479 );
			} else if ( i === 4 ) {
				hex = getRandomHex( 32768, 49151 );
			} else {
				hex = getRandomHex( 0, 65535 );
			}

			while ( hex.length < 4 ) {
				hex = '0' + hex;
			}

			guid += hex;
		}

		return entityId + '$' + guid;
	};

	/**
	 * Форматирование дат в datavalue для викиданных
	 */
	var wdeCreateTimeSnak = function ( timestamp, forceJulian ) {
		if ( !timestamp ) {
			return;
		}
		var months = [ 'январь', 'февраль', 'март', 'апрель', 'май', 'июнь',
			'июль', 'август', 'сентябрь', 'октябрь', 'ноябрь', 'декабрь' ];
		var monthsGen = [ 'января', 'февраля', 'марта', 'апреля', 'мая', 'июня',
			'июля', 'августа', 'сентября', 'октября', 'ноября', 'декабря' ];
		var result = { timezone: 0, before: 0, after: 0 };
		var isoDate;
		var dateParts;
		
		if ( timestamp.match ( /\s\([^\)]*\)\s/ ) ) {
			forceJulian = true;
		}
		timestamp = timestamp.replace( /\([^\)]*\)/, '' ).trim();

		var isBCE = false;	
		var bceMatch = timestamp.match( new RegExp( wdeConfig.reBCE ) );
		if ( bceMatch ) {
			isBCE = true;
			timestamp = timestamp.replace( bceMatch[ 0 ], '' ).trim();
		} else {
			var ceMatch = timestamp.match( new RegExp( wdeConfig.reCE ) );
			if ( ceMatch ) {
				timestamp = timestamp.replace( ceMatch[ 0 ], '' ).trim();
			}
		}

		if ( dateParts = timestamp.match( /^([ivx]{1,5})\s*(века?|в\.)?$/i ) ) {
			isoDate = new Date( 0 );
			isoDate.setFullYear( centuries.indexOf( dateParts[ 1 ].toUpperCase() ) * 100 + 1 );
			result.precision = 7;
		} else if ( dateParts = timestamp.match( new RegExp( '^(' + months.join( '|' ) + ')\\s+([12]\\d{3})$' ) ) ) {
			isoDate = new Date( Date.UTC( dateParts[ 2 ], months.indexOf( dateParts[ 1 ] ) ) );
			result.precision = 10;
		} else if ( dateParts = timestamp.match( new RegExp( '(\\d{1,2})\\s+(' + monthsGen.join( '|' ) + ')[,\\s]+([12]\\d{3})(?:\\sгод|\.?$)' ) ) ) {
			isoDate = new Date( Date.UTC( dateParts[ 3 ], monthsGen.indexOf( dateParts[ 2 ] ), dateParts[ 1 ] ) );
			result.precision = 11;
		} else if ( dateParts = timestamp.match( /^(\d{1,2})\.(\d{1,2})\.(\d{2,4})$/ ) ) {
			isoDate = new Date( Date.UTC( dateParts[ 3 ] < 100 ? 1900 + parseInt( dateParts[ 3 ] ) : dateParts[ 3 ], dateParts[ 2 ] - 1, dateParts[ 1 ] ) );
			result.precision = 11;
		} else if ( dateParts = timestamp.match( /^(\d{4})-(\d{1,2})-(\d{1,2})/ ) ) {
			isoDate = new Date( Date.UTC( dateParts[ 1 ] < 100 ? 1900 + parseInt( dateParts[ 1 ] ) : dateParts[ 1 ], dateParts[ 2 ] - 1, dateParts[ 3 ] ) );
			result.precision = 11;
		} else if ( dateParts = timestamp.match( /(\d{3,4})-е/ ) ) {
			isoDate = new Date( Date.UTC( dateParts[ 1 ], 0 ) );
			result.precision = 8;
		} else if ( dateParts = timestamp.match( /(\d{3,4})/ ) ) {
			isoDate = new Date( Date.UTC( dateParts[ 1 ], 0 ) );
			result.precision = 9;
		} else if ( timestamp.match( /^(наст\.|настоящее|наше)\s+время$/ ) ) {
			return 'novalue';
		} else if ( timestamp.match( /^(\?|неизвестно)$/ ) ) {
			return 'somevalue';
		} else {
			return;
		}

		try {
			result.time = ( isBCE ? '-' : '+' ) + isoDate.toISOString().replace( /\.000Z/, 'Z' );
		} catch ( e ) {
			return;
		}
		if ( result.precision < 11 ) {
			result.time = result.time.replace( /-\d\dT/, '-00T' );
		}
		if ( result.precision < 10 ) {
			result.time = result.time.replace( /-\d\d-/, '-00-' );
		}
		
		result.calendarmodel = 'http://www.wikidata.org/entity/Q' +
			( forceJulian || isoDate < new Date( Date.UTC( 1582, 9, 15 ) ) ? '1985786' : '1985727' );
		return result;
	};

	/**
	 * Отображение ошибки
	 */
	var wdeErrorDialog = function ( title, message ) {
		var errorDialog = new OO.ui.MessageDialog();
		wdeWindowManager.addWindows( [ errorDialog ] );
		wdeWindowManager.openWindow( errorDialog, {
			title: title,
			message: message
		} );
	};

	/**
	 * Достаем url сноски
	 */
	var wdeGetReference = function ( $field ) {
		var references = [];
		var $notes = $field.find( 'sup.reference a' );
		for ( var i = 0; i < $notes.length; i++ ) {
			var $externalLinks = $( $notes[ i ].hash.replace( /[!"$%&'()*+,.\/:;<=>?@[\\\]^`{|}~]/g, '\\$&' ) + ' a[rel="nofollow"]' );
			for ( var j = 0; j < $externalLinks.length; j++ ) {
				var $externalLink = $( $externalLinks.get( j ) );
				if ( !$externalLink.attr( 'href' ).match( /(wikipedia.org|webcitation.org|archive.is)/ ) ) {
					var source = {
						snaks: {
							P854: [ {
								property: 'P854',
								datatype: 'url',
								snaktype: 'value',
								datavalue: {
									type: 'string',
									value: $externalLink.attr( 'href' ).replace( /^\/\//, 'https://' )
								}
							} ]
						}
					};

					// P813
					var $accessed = $externalLinks.parent().find( 'small:contains("Проверено")' );
					if ( $accessed.length ) {
						var accessDate = wdeCreateTimeSnak( $accessed.first().text() );
						if ( accessDate ) {
							source.snaks.P813 = [ {
								property: 'P813',
								datatype: 'time',
								snaktype: 'value',
								datavalue: {
									type: 'time',
									value: accessDate
								}
							} ];
						}
					}

					// P1065 + P2960
					var $archiveLinks = $externalLinks.filter( 'a:contains("Архивировано")' );
					if ( $archiveLinks.length ) {
						var $archiveLink = $archiveLinks.first();
						source.snaks.P1065 = [ {
							property: 'P1065',
							datatype: 'url',
							snaktype: 'value',
							datavalue: {
								type: 'string',
								value: $archiveLink.attr( 'href' ).replace( /^\/\//, 'https://' )
							}
						} ];

						var archiveDate = wdeCreateTimeSnak( $archiveLink.parent().text().replace( 'Архивировано', '' ).trim() );
						if ( archiveDate ) {
							source.snaks.P2960 = [ {
								property: 'P2960',
								datatype: 'time',
								snaktype: 'value',
								datavalue: {
									type: 'time',
									value: archiveDate
								}
							} ];
						}
					}

					references.push( source );
					break;
				}
			}
		}
		references.push( { snaks: wdeConfig.references } );
		return references;
	};

	/**
	 * Отформатировать источники для показа
	 */
	var wdeFormatDomains = function ( references ) {
		var $result = $( '<sup>' );
		for ( var i = 0; i < references.length; i++ ) {
			var p854 = references[ i ].snaks.P854;
			if ( p854 ) {
				var domain = p854[ 0 ].datavalue.value.replace( 'http://', '' ).replace( 'https://', '' ).replace( 'www.', '' );
				if ( domain.indexOf( '/' ) > 0 ) {
					domain = domain.substr( 0, domain.indexOf( '/' ) );
				}
				$result.append( $( '<a>' ).attr( 'href', p854[ 0 ].datavalue.value ).text( '[' + domain + ']' ) );
			}
		}
		return $result;
	};

	/**
	 * Форматирование wikidata values для показа пользователю
	 */
	var wdeFormatDataValue = function ( datavalue ) {
		var $label = $( '<span>' );
		switch ( datavalue.type ) {
			case 'time':
				var bceMark = ( datavalue.value.time.charAt( 0 ) === '-' ? ' до н. э.' : '' );

				if ( datavalue.value.precision === 7 ) {
					$label.text( centuries[ Math.floor( (datavalue.value.time.substr( 1, 4 ) - 1) / 100 ) ] + ' век' + bceMark );
					break;
				}
				var options = {};
				if ( datavalue.value.precision > 7 ) {
					options.year = 'numeric';
				}
				if ( datavalue.value.precision > 9 ) {
					options.month = 'long';
				}
				if ( datavalue.value.precision > 10 ) {
					options.day = 'numeric';
				}
				var parsedDate = new Date( Date.parse( datavalue.value.time.substring( 1 ).replace( /-00/g, '-01' ) ) );
				$label.text( parsedDate.toLocaleString( 'tt-ru', options ) + ( datavalue.value.precision === 8 ? '-е' : '' ) + bceMark );
				break;

			case 'quantity':
				$label.append( $( '<strong>' ).text( datavalue.value.amount ) );
				if ( datavalue.value.bound ) {
					$label.append( $( '<span>' ).text( ' ± ' + datavalue.value.bound ) );
				}
				if ( datavalue.value.unit !== '1' ) {
					var unitId = datavalue.value.unit.substr( datavalue.value.unit.indexOf( 'Q' ) );
					var name = ( ( wdeConfig.units[ unitId ] || {} ).label || {} ).value || unitId;
					var description = ( ( wdeConfig.units[ unitId ] || {} ).description || {} ).value || 'Невозможно подгрузить описание';
					$label.append( '&nbsp;' ).append( $( '<abbr>' ).attr( 'title', description ).text( name ) );
				}
				break;

			case 'wikibase-entityid':
				$label.append( $( '<strong>' ).text( datavalue.value.label ? datavalue.value.label : datavalue.value.id ) )
					.append( datavalue.value.description ? ' — ' + datavalue.value.description : '' );
				break;
		}

		for ( var propertyId in datavalue.qualifiers ) {
			if ( !datavalue.qualifiers.hasOwnProperty( propertyId ) ) {
				continue;
			}
			if ( propertyId === 'P1480' && datavalue.qualifiers[ propertyId ][ 0 ].datavalue.value.id === 'Q5727902' ) {
				$label.prepend( $( '<abbr>' ).attr( 'title', 'обстоятельства источника = около' ).text( 'ок.' ), ' ' );
			} else {
				var formatted = wdeFormatDataValue( datavalue.qualifiers[ propertyId ][ 0 ].datavalue );
				if ( formatted && $( '<span>' ).append( formatted ).text() ) {
					$label.append( $( '<span>' ).text( ' (' ).append( formatted ).append( ')' ) );
				}
			}
		}

		return $label;
	};

	var wdeGetWikidataIds = function ( titles, callback ) {
		var languages = titles.map( function ( item ) {
			return item.language;
		} );
		languages = $.merge( languages, wdeConfig.languages );
		languages = $.unique( languages );

		var sites = titles.map( function ( item ) {
			return item.project;
		} );

		wdApi.get( {
			action: 'wbgetentities',
			sites: sites,
			languages: languages,
			props: [ 'labels', 'descriptions', 'claims' ],
			titles: titles.map( function ( item ) {
				return item.label;
			} )
		} ).done( function ( data ) {
			if ( data.success ) {
				var valuesObj = {};
				var value;
	
				for ( var entityId in data.entities ) {
					if ( !data.entities.hasOwnProperty( entityId ) || !entityId.match( /^Q/ ) ) {
						continue;
					}
	
					var entity = data.entities[ entityId ];
					var label = entity.labels.tt || entity.labels.en || entity.labels[ Object.keys( entity.labels )[ 0 ] ] || '';
					var description = entity.descriptions.tt || entity.descriptions.en || entity.descriptions[ Object.keys( entity.descriptions )[ 0 ] ] || '';
	
					if ( ( ( ( ( ( ( ( entity || {} ).claims || {} ).P31 || [] )[ 0 ] || {} ).mainsnak || {} ).datavalue || {} ).value || {} ).id === 'Q4167410' ) {
						continue; //пропускаем неоднозначности
					}
	
					var subclassFound = false;
					var subclassEntity = null;
					for ( var candidateId in data.entities ) {
						if ( !data.entities.hasOwnProperty( candidateId ) || !candidateId.match( /^Q/ ) || entityId === candidateId ) {
							continue;
						}
	
						subclassFound = [ 'P17', 'P31', 'P131', 'P279', 'P361' ].find( function ( propertyId ) {
							var values = ( ( ( data.entities[ candidateId ] || {} ).claims || {} )[ propertyId ] || [] );
							return values.find( function ( statement ) {
								var result = ( ( ( statement.mainsnak || {} ).datavalue || {} ).value || {} ).id === entityId;
								if ( result ) {
									subclassEntity = data.entities[ candidateId ];
								}
								return result;
							} );
						} );
	
						if ( subclassFound ) {
							break;
						}
					}
	
					if ( subclassFound ) {
						if ( subclassEntity ) {
							var subclassLabel = subclassEntity.labels.tt || subclassEntity.labels.en || subclassEntity.labels[ Object.keys( subclassEntity.labels )[ 0 ] ];
							mw.notify( 'Значение «' + label.value + '» исключено, уже имеется более точное: «' + subclassLabel.value + '»', {
								type: 'warn',
								tag: 'wikidataInfoboxExport-warn-precise'
							} );
						}
						continue; //пропускаем записи для которых есть более точные
					}
	
					value = {
						wd: {
							type: 'wikibase-entityid',
							value: {
								id: entityId,
								label: label ? label.value : label,
								description: description ? description.value : description
							}
						}
					};
					if ( label ) {
						var results = titles.filter( function ( item ) {
							return item.label.toLowerCase() === label.value.toLowerCase();
						} );
						if ( results.length === 1 ) {
							value.wd.qualifiers = results[ 0 ].qualifiers;
						}
					}
					value.label = wdeFormatDataValue( value.wd );
					delete value.wd.value.label;
					delete value.wd.value.description;
					valuesObj[ entityId ] = value;
				}

				callback( valuesObj );
			};
		} );		
	};

	var wdeParseItems = function ( $content, $wrapper, callback ) {
		var titles = [];

		var wdeProcessWbGetItems = function ( valuesObj ) {
			var values = $.map( valuesObj, function( value, index ) {
			    return [ value ];
			} );
			if ( values.length === 1 ) {
				value = values.pop();
				wdeAddQualifiers( $wrapper, value.wd, value.label, function( value ) {
					callback( [ value ] );
				} );
			} else if ( callback ) {
				callback( values );
			}
		};
		var fixedValues = [
			{ property: 'P21', regexp: /жен/, item: 'Q6581072', label: 'женский' },
			{ property: 'P21', regexp: /муж/, item: 'Q6581097', label: 'мужской' },
			{ property: 'P552', regexp: /прав/, item: 'Q3039938', label: 'правша' },
			{ property: 'P552', regexp: /лев/, item: 'Q789447', label: 'левша' },
			{ property: 'P423', regexp: /прав/, item: 'Q10927630', label: 'праворукий удар' },
			{ property: 'P423', regexp: /лев/, item: 'Q10927615', label: 'леворукий удар' }
		];
		for ( var k = 0; k < fixedValues.length; k++ ) {
			if ( $content.attr( 'data-wikidata-property-id' ) === fixedValues[ k ].property && $content.text().match( fixedValues[ k ].regexp ) ) {
				var result = { success: true, entities: {} };
				result.entities[ fixedValues[ k ].item ] = {
					labels: { tt: { value: fixedValues[ k ].label } },
					descriptions: {}
				};
				wdeProcessWbGetItems( result );
				return;
			}
		}

		var $links = $content.find( 'a[title][class!=image][class!=new]' );
		var redirects = [];

		if ( $links.length ) {
			for ( var j = 0; j < $links.length; j++ ) {
				var $link = $( $links[ j ] );
				if ( $link.parents( '[data-wikidata-qualifier-id]' ).length ) {
					continue;
				}
				var extractedUrl = decodeURIComponent( $link.attr( 'href' ) ).replace( /^.*\/wiki\//, '' );
				if ( extractedUrl ) {
					extractedUrl = extractedUrl.replace( /_/g, ' ' ).trim();
					var value = {
						label: extractedUrl.charAt( 0 ).toUpperCase() + extractedUrl.substr( 1, extractedUrl.length - 1 ),
						language: wdeConfig.language,
						project: wdeConfig.project,
						qualifiers: {}
					};
					var match = $links[ j ].innerHTML.match( /(\d\d\d\d)\s*(?:год|г\.?)/ );
					if ( !match ) {
						match = $links[ j ].innerHTML.match( / — (\d\d\d\d)/ );
					}
					var extractedYear = match ? wdeCreateTimeSnak( match[ 1 ] ) : wdeCreateTimeSnak( ( $links[ j ].nextSibling || {} ).textContent );
					if ( extractedYear ) {
						value.qualifiers.P585 = [ {
							property: 'P585',
							datatype: 'time',
							snaktype: 'value',
							datavalue: {
								type: 'time',
								value: extractedYear
							}
						} ];
					}
					if ( $link.hasClass( 'extiw' ) ) {
						var m = $links[ j ].getAttribute( 'href' ).match( /^https:\/\/([a-z\-]+)\.(wik[^\.]+)\./ );
						if ( m && m[ 2 ] !== 'wikimedia' ) {
							value.language = m[ 1 ];
							value.project = m[ 1 ] + m[ 2 ].replace( 'wikipedia', 'wiki' );
						}
					}
					if ( $link.hasClass( 'mw-redirect' ) ) {
						redirects.push( extractedUrl );
					}
					titles.push( value );
					if ( $( $links[ j ] ).find( 'img' ) ) {
						redirects.push( extractedUrl );
					}
				}
			}
		} else if ( $content.text().trim() ) {
			// Если ссылок не нашлось, то пробуем искать статью по тексту
			var parts = $content.text().split( /[\n,;]+/ );
			for ( var i in parts ) {
				var year = '';
				var articleTitle = parts[ i ].replace( /\([^)]*\)/, function ( match ) {
					year = match.replace( /\(\)/, '' );
					return '';
				} ).trim();
				if ( articleTitle ) {
					var value = {
						label: articleTitle.charAt( 0 ).toUpperCase() + articleTitle.substr( 1, articleTitle.length - 1 ),
						language: wdeConfig.language,
						project: wdeConfig.project,
						qualifiers: {}
					};
					if ( wdeCreateTimeSnak( year ) ) {
						value.qualifiers.P585 = [ {
							property: 'P585',
							datatype: 'time',
							snaktype: 'value',
							datavalue: {
								type: 'time',
								value: wdeCreateTimeSnak( year )
							}
						} ];
					}
					titles.push( value );
				}
			}
			titles = $.unique( titles );
		}
		if ( redirects.length ) {
			api.get( {
				action: 'query',
				redirects: 1,
				titles: redirects
			} ).done( function ( data ) {
				if ( data.query && data.query.redirects ) {
					for ( var i = 0; i < data.query.redirects.length; i++ ) {
						for ( var j = 0; j < titles.length; j++ ) {
							var lcTitle = titles[ j ].label.substr( 0, 1 ).toLowerCase() + titles[ j ].label.substr( 1 );
							var lcRedirect = data.query.redirects[ i ].from.substr( 0, 1 ).toLowerCase() + data.query.redirects[ i ].from.substr( 1 );
							if ( lcTitle === lcRedirect ) {
								titles.splice( j + 1, 0, {
									label: data.query.redirects[ i ].to,
									language: wdeConfig.language,
									project: wdeConfig.project,
									year: titles[ j ].year
								} );
								j++;
							}
						}
					}
				}

				wdeGetWikidataIds( titles, wdeProcessWbGetItems );
			} );
		} else {
			wdeGetWikidataIds( titles, wdeProcessWbGetItems );
		}
	};

	/**
	 * Распарсивание количества и (опционально) точности
	 */
	var wdeParseQuantity = function ( text, forceInteger ) {
		var out = {
			value: {},
		};
		text = text.replace( /,/g, '.' ).replace( /[−|–]/g, '-' );

		// Sourcing circumstances (P1480) = circa (Q5727902)
		var circaMatch = text.match( new RegExp( wdeConfig.reCirca ) );
		if ( circaMatch ) {
			out.qualifiers = {
				P1480: [ {
					property: 'P1480',
					snaktype: 'value',
					datavalue: {
						type: 'wikibase-entityid',
						value: { id: 'Q5727902' }
					}
				} ],
			};
			text = text.replace( circaMatch[ 0 ], '' );
		}

		var magnitude = 0;
		if ( text.match( /тыс/ ) ) {
			magnitude += 3;
		} else if ( text.match( /млн|\dM(?:\s|$)|миллион|million/ ) ) {
			magnitude += 6;
		} else if ( text.match( /млрд|billion/ ) ) {
			magnitude += 9;
		} else if ( text.match( /трлн/ ) ) {
			magnitude += 12;
		} else {
			var match = text.match( /[\*|·]10(-?\d+)/ );
			if ( match ) {
				text = text.replace( /[\*|·]10(-?\d+)/, '' );
				magnitude += parseInt( match[ 1 ] );
			}
		}
		var decimals = text.split( '±' );
		if ( magnitude === 0 && forceInteger ) {
			decimals[ 0 ] = decimals[ 0 ].replace( /\./g, '' );
		}

		var amount = parseFloat( decimals[ 0 ].replace( /[^0-9.+-]/g, '' ) );
		
		if ( isNaN( amount ) ) {
			return;
		}

		var parts = amount.toString().match( /(\d+)\.(\d+)/ );
		var integral = parts ? parts[ 1 ].length : amount.toString().length;
		var fractional = parts ? parts[ 2 ].length : 0;
		if ( magnitude >= 0 ) {
			if ( magnitude <= fractional ) {
				out.value.amount = ( ( '1e' + magnitude ) * amount ).toFixed( fractional - magnitude );
			} else {
				out.value.amount = ( ( '1e' + fractional ) * amount ).toFixed( 0 ).replace( /$/, new Array( magnitude - fractional + 1 ).join( '0' ) );
			}
		} else {
			if ( magnitude >= -integral ) {
				out.value.amount = ( ( '1e' + magnitude ) * amount ).toFixed( fractional - magnitude );
			} else {
				out.value.amount = ( ( '1e-' + integral ) * amount ).toFixed( integral + fractional ).replace( /0\./, '0.' + new Array( -magnitude - integral + 1 ).join( '0' ) );
			}
		}

		if ( decimals.length > 1 ) {
			var bound = parseFloat( decimals[ 1 ].replace( /[^0-9.+-]/g, '' ) );

			if ( !isNaN( bound ) ) {
				if ( decimals[ 1 ].indexOf( '%' ) > 0 ) {
					bound = amount * bound / 100;
				} else {
					parts = bound.toString().match( /(\d+)\.(\d+)/ );
					integral = parts ? parts[ 1 ].length : amount.toString().length;
					fractional = parts ? parts[ 2 ].length : 0;
				}
				if ( magnitude >= 0 ) {
					if ( magnitude <= fractional ) {
						out.value.lowerBound = ( ( '1e' + magnitude ) * ( amount - bound ) ).toFixed( fractional - magnitude );
						out.value.upperBound = ( ( '1e' + magnitude ) * ( amount + bound ) ).toFixed( fractional - magnitude );
						out.value.bound = ( ( '1e' + magnitude ) * bound ).toFixed( fractional - magnitude ); // нужно для показа пользователю
					} else {
						out.value.lowerBound = ( ( '1e' + fractional) * ( amount - bound ) ).toFixed( 0 ).replace( /$/, new Array( magnitude - fractional + 1 ).join( '0' ) );
						out.value.upperBound = ( ( '1e' + fractional) * ( amount + bound ) ).toFixed( 0 ).replace( /$/, new Array( magnitude - fractional + 1 ).join( '0' ) );
						out.value.bound = ( ( '1e' + fractional ) * bound ).toFixed( 0 ).replace( /$/, new Array( magnitude - fractional + 1 ).join( '0' ) );
					}
				} else {
					if ( magnitude >= -integral ) {
						out.value.lowerBound = ( ( '1e' + magnitude ) * ( amount - bound ) ).toFixed( fractional - magnitude );
						out.value.upperBound = ( ( '1e' + magnitude ) * ( amount + bound ) ).toFixed( fractional - magnitude );
						out.value.bound = ( ( '1e' + magnitude ) * bound ).toFixed( fractional - magnitude );
					} else {
						out.value.lowerBound = ( ( '1e-' + integral ) * ( amount - bound ) ).toFixed( integral + fractional ).replace( /0\./, '0.' + new Array( -magnitude - integral + 1 ).join( '0' ) );
						out.value.upperBound = ( ( '1e-' + integral ) * ( amount + bound ) ).toFixed( integral + fractional ).replace( /0\./, '0.' + new Array( -magnitude - integral + 1 ).join( '0' ) );
						out.value.bound = ( ( '1e-' + integral ) * bound ).toFixed( integral + fractional ).replace( /0\./, '0.' + new Array( -magnitude - integral + 1 ).join( '0' ) );
					}
				}
			}
		}
		return out;
	};

	/**
	 * Распознавание единиц измерения в параметре карточки и его заголовке
	 */
	var wdeRecognizeUnits = function ( text, units, label ) {
		if ( Array.isArray( units ) && units.length === 0 ) {
			return [ '1' ];
		}
		var result = [];
		for ( var idx in units ) {
			if ( !units.hasOwnProperty( idx ) ) {
				continue;
			}
			var item = parseInt( idx ) >= 0 ? units[ idx ] : idx;
			var search = wdeConfig.units[ item ].search;
			for ( var j = 0; j < search.length; j++ ) {
				var expr = search[ j ];
				if ( search[ j ].charAt( 0 ) !== '^' ) {
					expr = '[\\d\\s\\.]' + expr;
					if ( search[ j ].length < 5 ) {
						expr = expr + '\\.?$';
					}
				}
				if ( text.match( new RegExp( expr, 'i' ) ) ) {
					result.push( item );
					break;
				} else if ( search[ j ].charAt( 0 ) !== '^' && label && label.match( new RegExp( '\\s' + search[ j ] + ':?$', 'i' ) ) ) {
					result.push( item );
					break;
				}
			}
		}
		return result;
	};

	/**
	 * Создание всех утверждений в Викиданных и отметка свойства экcпортированным
	 */
	var wdeCreateClaims = function ( propertyId, values, refUrl, revIds ) {
		var value = values.shift();
		revIds = revIds || [];
		if ( !value ) {
			// Все утверждения добавлены - переходим к установке меток
			wdeAddTags( propertyId, revIds );
			return;
		} else {
			value = JSON.parse( value );
		}
		var datatype = wdeConfig.properties[ propertyId ].datatype;
		var mainsnak = value.value.toString().match( /^(novalue|somevalue)$/ ) ? {
			snaktype: value.value,
			property: propertyId
		} : {
			snaktype: 'value',
			property: propertyId,
			datavalue: {
				type: wdeTypesMapping[ datatype ] ? wdeTypesMapping[ datatype ] : datatype,
				value: value.value
			}
		};
		var claim = {
			type: 'statement',
			mainsnak: mainsnak,
			id: wdeClaimGuid( mw.config.get( 'wgWikibaseItemId' ) ),
			references: refUrl,
			rank: 'normal'
		};
		if ( value.qualifiers ) {
			claim.qualifiers = value.qualifiers;
		}

		wdApi.postWithToken( 'csrf', {
			action: 'wbsetclaim',
			claim: JSON.stringify( claim ),
			baserevid: wdeBaseRevId
		} ).done( function ( claimData ) {
			if ( claimData.success ) {
				var valuesLeftStr = values.length ? ' (осталось ' + values.length + ')' : '';
				mw.notify( 'Значение свойства ' + propertyId + ' успешно добавлено в Викиданные' + valuesLeftStr, {
					tag: 'wikidataInfoboxExport-success'
				} );

				wdeBaseRevId = claimData.pageinfo.lastrevid;
				revIds.push( wdeBaseRevId );
				wdeCreateClaims( propertyId, values, refUrl, revIds );
			} else {
				wdeErrorDialog( 'Ошибка при сохранении', JSON.stringify( claimData ) );
			}
		} );
	};

	/**
	 * Установка меток гаджета для сделанных правок
	 * FIXME: Добавлять метки сразу, когда будет исправлен [[phab:T155109]]
	 */
	var wdeAddTags = function ( propertyId, revIds ) {
		wdApi.postWithToken( 'csrf', {
			action: 'tag',
			add: 'InfoboxExport gadget',
			revid: revIds
		} ).done( function ( data ) {
			var success = false;
			if ( data.tag ) {
				success = true;
				for ( var i = 0; i < data.tag.length; i++ ) {
					if ( data.tag[ i ].status !== 'success' ) {
						success = false;
						break;
					}
				}
			}
			if ( success ) {
				mw.notify( 'Метки правок в Викиданных успешно установлены', {
					tag: 'wikidataInfoboxExport-tags-success'
				} );

				$( '.no-wikidata[data-wikidata-property-id=' + propertyId + ']' )
					.removeClass( 'no-wikidata' )
					.off( 'dblclick', wdeClickEvent );
			} else {
				mw.notify( 'Ошибка при установке метки правок в Викиданных', {
					type: 'warn',
					tag: 'wikidataInfoboxExport-tags-error'
				} );
			}
		} );
	};

	/**
	 * Обёртка для предзагрузки информации о свойствах, исключающая уже загруженные.
	 */
	var wdeLoadProperties = function ( propertyIds ) {
		if ( !propertyIds || !propertyIds.length ) {
			return;
		}
		
		var realPropertyIds = [];
		for ( var i in propertyIds ) {
			var propertyId = propertyIds[ i ];
			if ( propertyId && wdeConfig.properties[ propertyId ] === undefined ) {
				realPropertyIds.push( propertyId );
			}
		}
		
		if ( realPropertyIds.length ) {
			wdeRealLoadProperties( realPropertyIds );
		}
	};

	/**
	 * Предзагрузка информации о всех свойствах.
	 */
	var wdeRealLoadProperties = function ( propertyIds ) {
		if ( !propertyIds || !propertyIds.length ) {
			return;
		}

		var units = [];
		wdApi.get( {
			action: 'wbgetentities',
			languages: wdeConfig.languages,
			props: [ 'labels', 'datatype', 'claims' ],
			ids: propertyIds
		} ).done( function ( data ) {
			if ( !data.success ) {
				return;
			}

			for ( var propertyId in data.entities ) {
				if ( !data.entities.hasOwnProperty( propertyId ) ) {
					continue;
				}
				var entity = data.entities[ propertyId ];
				var label = entity.labels[ wdeConfig.language ] ? entity.labels[ wdeConfig.language ].value : entity.labels.en.value;
				wdeConfig.properties[ propertyId ] = {
					datatype: entity.datatype,
					label: label.charAt( 0 ).toUpperCase() + label.slice( 1 ),
					constraints: { qualifier: [] },
					units: []
				};
				if ( propertyId === 'P1128' || propertyId === 'P2196' ) {
					wdeConfig.properties[ propertyId ].constraints.integer = 1;
				}
				if ( entity.claims ) {
					// Ограничения свойства
					if ( entity.claims.P2302 ) {
						for ( var i in entity.claims.P2302 ) {
							switch ( ( ( ( ( entity.claims.P2302[ i ] || {} ).mainsnak || {} ).datavalue || {} ).value || {} ).id ) {
								case 'Q19474404':
								case 'Q21502410':
									wdeConfig.properties[ propertyId ].constraints.unique = 1;
									break;
							}
						}
					}
					// "Обязательный" квалификатор
					if ( entity.claims.P1646 ) {
						for ( var i in entity.claims.P1646 ) {
							var qualifierId = ( ( ( ( entity.claims.P1646[ i ] || {} ).mainsnak || {} ).datavalue || {} ).value || {} ).id;
							if ( qualifierId ) {
								wdeConfig.properties[ propertyId ].constraints.qualifier.push( qualifierId.toString() );
							}
						}
					}
					// Единицы измерения
					if ( entity.claims.P2237 ) {
						for ( var i in entity.claims.P2237 ) {
							var unitId = ( ( ( ( entity.claims.P2237[ i ] || {} ).mainsnak || {} ).datavalue || {} ).value || {} ).id;
							if ( unitId && unitId != 'Q21027105' ) {
								wdeConfig.properties[ propertyId ].units.push( unitId );
								units.push( unitId );
							}
						}
					}
				}
			}

			wdApi.get( {
				action: 'wbgetentities',
				languages: wdeConfig.languages,
				props: [ 'labels', 'descriptions', 'aliases', 'claims' ],
				ids: $.unique( units )
			} ).done( function ( unitData ) {
				if ( !unitData.success ) {
					return;
				}

				for ( var unitId in unitData.entities ) {
					var unit = unitData.entities[ unitId ];
					var unitSearch = wdeConfig.units[ unitId ] ? wdeConfig.units[ unitId ].search : [];
					if ( !wdeConfig.units[ unitId ] ) {
						wdeConfig.units[ unitId ] = {};
					}

					// Метка
					if ( unit.labels ) {
						wdeConfig.units[ unitId ].label = unit.labels[ wdeConfig.language ] ||
							unit.labels.en ||
							unit.labels[ Object.keys( unit.labels )[ 0 ] ];

						if ( unit.labels[ wdeConfig.language ] ) {
							unitSearch.push( unit.labels[ wdeConfig.language ].value.replace( /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&' ) );
						}
					}

					// Описание
					if ( unit.descriptions ) {
						wdeConfig.units[ unitId ].description = unit.descriptions[ wdeConfig.language ] ||
							unit.descriptions.en ||
							unit.descriptions[ Object.keys( unit.labels )[ 0 ] ];
					}

					// Алиасы
					if ( unit.aliases && unit.aliases[ wdeConfig.language ] ) {
						for ( var i in unit.aliases[ wdeConfig.language ] ) {
							unitSearch.push( unit.aliases[ wdeConfig.language ][ i ].value.replace( /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&' ) );
						}
					}

					// Обозначение единиц (P558)
					if ( unit.claims && unit.claims.P558 ) {
						for ( var i in unit.claims.P558 ) {
							var claim = unit.claims.P558[ i ];
							if ( claim.mainsnak &&
								claim.mainsnak.datavalue &&
								claim.mainsnak.datavalue.value
							) {
								unitSearch.push( claim.mainsnak.datavalue.value.replace( /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&' ) );
							}
						}
					}
					wdeConfig.units[ unitId ].search = unitSearch;
					
					localStorage.setItem( 'wdeConfig', JSON.stringify( wdeConfig ) );
				}
			} );
		} );
	};

	var wdeCheckForMisLang = function ( wd ) {
		var lang = wd.value.language;
		if ( 'misLang' in wdeConfig && lang in wdeConfig.misLang ) {
			wd.value.language = 'mis';
			if ( !( 'qualifiers' in wd ) ) {
				wd.qualifiers = {};
			}
			wd.qualifiers.P585 = [ {
				property: 'P2439',
				snaktype: 'value',
				datavalue: {
					type: 'wikibase-entityid',
					value: { id: wdeConfig.misLang[ lang ] }
				}
			} ];
		}
		
		return wd;
	};

	var wdeAddQualifiers = function ( $field, value, $label, callback ) {
		var $qualifiers = $field.find( '[data-wikidata-qualifier-id]' );
		if ( $qualifiers.length ) {
			$label = $( '<div>' ).append( $label );
		}

		var addQualifierValue = function ( qualifierId, qualifierValue, qualifierLabel ) {
			if ( value.qualifiers === undefined ) {
				value.qualifiers = {};
			}
			if ( value.qualifiers[ qualifierId ] === undefined ) {
				value.qualifiers[ qualifierId ] = [];
			}
			var datatype = wdeConfig.properties[ qualifierId ].datatype;
			value.qualifiers[ qualifierId ].push( {
				snaktype: 'value',
				property: qualifierId,
				datavalue: {
					type: wdeTypesMapping[ datatype ] ? wdeTypesMapping[ datatype ] : datatype,
					value: qualifierValue
				}
			} );
			$label.append( $( '<p>' )
				.append( $( '<a>' )
					.attr( 'href', '//www.wikidata.org/wiki/Property:' + qualifierId )
					.text( wdeConfig.properties[ qualifierId ].label )
				)
				.append( $( '<span>' ).text( ': ' ) )
				.append( qualifierLabel )
			);
		};

		var qualifierTitles = {};
		for ( var q = 0; q < $qualifiers.length; q++ ) {
			var $qualifier = $( $qualifiers[ q ] );
			var qualifierId = $qualifier.data( 'wikidata-qualifier-id' );
			var qualifierValue = $qualifier.text().replace( '\n', ' ' ).trim();
			switch ( wdeConfig.properties[ qualifierId ].datatype ) {
				case 'monolingualtext':
					qualifierValue = {
						text: $qualifier.text().replace( '\n', ' ' ).trim(),
						language: $qualifier.attr( 'lang' ) || wdeConfig.language
					};
					addQualifierValue( qualifierId, qualifierValue, $qualifier.text() );
					break;
	
				case 'string':
					qualifierValue = $qualifier.text().replace( '\n', ' ' ).trim();
					addQualifierValue( qualifierId, qualifierValue, $qualifier.text() );
					break;
	
				case 'time':
					qualifierValue = wdeCreateTimeSnak( qualifierValue );
					addQualifierValue( qualifierId, qualifierValue, $qualifier.text() );
					break;
	
				case 'wikibase-item':
					if ( qualifierTitles[ qualifierId ] === undefined ) {
						qualifierTitles[ qualifierId ] = [];
					}
					
					var $links = $qualifier.find( 'a[title][class!=image][class!=new]' );
					if ( $links.length ) {
						for ( var l = 0; l < $links.length; l++ ) {
							var $link = $( $links[ l ] );
							var extractedUrl = decodeURIComponent( $link.attr( 'href' ) ).replace( /^.*\/wiki\//, '' );
							if ( extractedUrl ) {
								extractedUrl = extractedUrl.replace( /_/g, ' ' ).trim();
								var title = {
									label: extractedUrl.charAt( 0 ).toUpperCase() + extractedUrl.substr( 1, extractedUrl.length - 1 ),
									language: wdeConfig.language,
									project: wdeConfig.project,
									qualifiers: {}
								};
								if ( $link.hasClass( 'extiw' ) ) {
									var m = $link.attr( 'href' ).match( /^https:\/\/([a-z\-]+)\.(wik[^\.]+)\./ );
									if ( m && m[ 2 ] !== 'wikimedia' ) {
										title.language = m[ 1 ];
										title.project = m[ 1 ] + m[ 2 ].replace( 'wikipedia', 'wiki' );
									}
								}
								qualifierTitles[ qualifierId ].push( title );
							}
						}
					} else {
						qualifierTitles[ qualifierId ].push( {
							label: qualifierValue.charAt( 0 ).toUpperCase() + qualifierValue.substr( 1, qualifierValue.length - 1 ),
							language: wdeConfig.language,
							project: wdeConfig.project,
							qualifiers: {}
						} );
					}
					break;
			}
		};

		var processItemTitles = function ( itemTitles, callback ) {
			if ( Object.keys( itemTitles ).length ) {
			    var qualifierId = Object.keys( itemTitles ).shift();
				var qualifierItemTitles = itemTitles[ qualifierId ];
				delete itemTitles[ qualifierId ];
				wdeGetWikidataIds( qualifierItemTitles, function ( valuesObj ) {
					for ( var entityId in valuesObj ) {
						var valueObj = valuesObj[ entityId ];
						addQualifierValue( qualifierId, valueObj.wd.value, valueObj.label );
					}
					processItemTitles( itemTitles, callback );
				} );
			} else {
				callback( {
					wd: value,
					label: $label
				} );
			}
		};
		processItemTitles( qualifierTitles, callback );
	};

	/**
	 * Парсинг значений из параметров перед выводом диалога
	 */
	var wdePrepareDialog = function ( $field, propertyId ) {
		var values = [];
		var datatype = wdeConfig.properties[ propertyId ].datatype;

		var $content = $field.clone();
		$content.find( 'sup.reference' ).remove();
		$content.find( '[style*="display:none"]' ).remove();

		var $wrapper = $content;
		var $row = $field.closest( 'tr' );
		if ( $row.length === 1 && $row.find( '[data-wikidata-property-id]' ).length === 1 ) {
			$wrapper = $row.clone();
		}

		switch ( datatype ) {
			case 'commonsMedia':
				var $imgs = $content.find( 'img' );
				$imgs.each( function () {
					var $img = $( this );
					var src = $img.attr( 'src' );
					if ( !src.match( /upload.wikimedia.org\/wikipedia\/commons/ ) ) {
						return;
					}
					var srcParts = src.split( '/' );
					var fileName = srcParts.pop();
					if ( fileName.match( /(?:^|-)\d+px-/ ) ) {
						fileName = srcParts.pop();
					}
					fileName = decodeURIComponent( fileName );
					fileName = fileName.replace( /_/g, ' ' );
					var value = { value: fileName };
					var $label = $img.clone()
						.attr( 'title', fileName )
						.css( 'border', '1px dashed #a2a9b1' );

					wdeAddQualifiers( $wrapper, value, $label, function( valueObj ) {
						values.push( valueObj );
					} );
				} );
				break;

			case 'external-id':
				var externalId = $content.data( 'wikidata-external-id' ) || $content.text();
				if ( propertyId === 'P345' ) { // IMDB
					externalId = $content.find( 'a' ).first().attr( 'href' );
					externalId = externalId.substr( externalId.lastIndexOf( '/', externalId.length - 2 ) ).replace( /\//g, '' );
				} else {
					externalId = externalId.toString().replace( /^ID\s/, '' ).replace( /\s/g, '' );
				}
				var sparql = 'SELECT * WHERE { ?item wdt:' + propertyId + ' "' + externalId + '" }';

				$.ajax( {
					url: 'https://query.wikidata.org/sparql?format=json&query=' + sparql,
					success: function ( data ) {
						var $label = $( '<code>' ).text( externalId );
						if ( data.results.bindings.length ) {
							var url = data.results.bindings[ 0 ].item.value;
							$label = $( '<span>' ).append( $( '<code>' ).text( externalId ) )
								.append( $( '<strong>' ).css( { 'color': 'red' } ).text( ' уже используется в ' ) )
								.append( $( '<a>' ).attr( 'href', url ).attr( 'target', '_blank' ).text( url.replace( /[^Q]*Q/, 'Q' ) ) );
						}
						wdeDialog( $field, propertyId, [ {
							wd: { value: externalId.toString() },
							label: $label
						} ], wdeGetReference( $content ) );
					}
				} );
				return;

			case 'string':
				var text = $content.data( 'wikidata-external-id' );
				if ( !text ) {
					text = $content.text();
				}
				var strings = text.toString().trim().split( /[\n,;]+/ );

				// Commons category
				if ( propertyId === 'P373' ) {
					var $link = $content.find( 'a[class="extiw"]' ).first();
					if ( $link.length ) {
						var url = $link.attr( 'href' );
						var value = url.substr( url.lastIndexOf( '/' ) + 1 )
							.replace( /_/g, ' ' )
							.replace( /^[Cc]ategory:/, '' )
							.replace( /\?.*$/, '' );
						value = decodeURIComponent( value );
						strings = [ value ];
					}
				}

				for ( var i in strings ) {
					var s = strings[ i ].replace( /\n/g, ' ' ).trim();
					if ( s ) {
						values.push( {
							wd: { value: s },
							label: $( '<code>' + s + '</code>' )
						} );
					}
				}
				break;

			case 'monolingualtext':
				var $items = $content.find( '[lang]' );
				$items.each( function () {
					var $item = $( this );
					values.push( {
						wd: wdeCheckForMisLang( {
							value: {
								text: $item.text().trim(),
								language: $item.attr( 'lang' ).trim()
							}
						} )
					} );
				} );
				if ( !values.length ) {
					var text = $content.text().trim();
					if ( text ) {
						var $items = mw.util.$content.find( '[lang]' );
						$items.each( function () {
							$item = $( this );
							if ( $item.text().trim().startsWith( text ) ) {
								values.push( {
									wd: {
										value: {
											text: text,
											language: $item.attr( 'lang' ).trim()
										}
									}
								} );
							}
						} );
					}
				}
				for ( var i in values ) {
					values[ i ].label = $( '<span>' )
						.append( $( '<span>' ).css( 'color', '#666' ).text( '(' + values[ i ].wd.value.language + ') ' ) )
						.append( $( '<strong>' ).text( values[ i ].wd.value.text ) );
				}
				break;

			case 'quantity':
				var text = $content.text().replace( /[\u00a0\u25bc\u25b2]/g, ' ' ).replace( /\s\(([^)]*\))/g, '' ).trim();

				// Хак для времени в формате hh:mm:ss
				var match = text.match( /^(?:(\d+):)?(\d+):(\d+)$/ );
				if ( match ) {
					var amount = 0;
					for ( var i = 1; i < match.length; i++ ) {
						if ( match[ i ] !== undefined ) {
							amount = amount * 60 + parseInt( match[ i ], 10 );
						}
					}
					
					text = amount + ' сек.';
				}

				var result = { wd: wdeParseQuantity( text, wdeConfig.properties[ propertyId ].constraints.integer ) };
				if ( !result.wd.value ) {
					break;
				}

				wdeAddQualifiers( $wrapper, result.wd, wdeFormatDataValue( result.wd ), function( valueObj ) {
					result = valueObj;
				} );

				if ( wdeConfig.properties[ propertyId ].constraints.qualifier.indexOf( 'P585' ) !== -1 ) {
					var yearMatch = $content.text().match( /\(([^)]*[12]\d\d\d)[,)\s]/ );
					if ( !yearMatch ) {
						yearMatch = $field.closest( 'tr' ).find( 'th' ).first().text().match( /\(([^)]*[12]\d\d\d)[,)\s]/ );
					}
					if ( yearMatch ) {
						if ( extractedDate = wdeCreateTimeSnak( yearMatch[ 1 ] ) ) {
							result.wd.qualifiers = {
								P585: [ {
									snaktype: 'value',
									property: 'P585',
									datavalue: {
										type: 'time',
										value: extractedDate
									}
								} ]
							};
						}
					}
				}

				var qualMatch = $content.text().match( /\(([^\)]*)/ );
				if ( qualMatch ) {
					qualQuantity = wdeParseQuantity( qualMatch[ 1 ] );
					if ( qualQuantity ) {
						var supportedProperties = [ 'P2076', 'P2077' ];
						for ( var j = 0; j < supportedProperties.length; j++ ) {
							var units = wdeRecognizeUnits( qualMatch[ 1 ], wdeConfig.properties[ supportedProperties[ j ] ].units );
							if ( units.length === 1 ) {
								qualQuantity.value.unit = 'http://www.wikidata.org/entity/' + units[ 0 ];
								if ( !result.wd.qualifiers ) {
									result.wd.qualifiers = {};
								}
								result.wd.qualifiers[ supportedProperties[ j ] ] = [ {
									snaktype: 'value',
									property: supportedProperties[ j ],
									datavalue: {
										type: 'quantity',
										value: qualQuantity.value
									}
								} ];
							}
						}
					}
				}

				var founded = wdeRecognizeUnits( text, wdeConfig.properties[ propertyId ].units, $field.closest( 'tr' ).find( 'th' ).first().text() );
				for ( var u = 0; u < founded.length; u++ ) {
					result.wd.value.unit = '1';
					if ( founded[ u ] !== '1' ) {
						result.wd.value.unit = 'http://www.wikidata.org/entity/' + founded[ u ];
						var item = wdeConfig.units[ founded[ u ] ];
					}
					result.wd.type = 'quantity';
					result.label = wdeFormatDataValue( result.wd );
					values.push( result );
				}
				break;

			case 'time':
				var value = wdeCreateTimeSnak( $content.text().toLowerCase().trim().replace( /\s+(года?|г\.?)$/, '' ),
					$content[ 0 ].outerHTML.includes( 'по юлианскому' ) );
				if ( value ) {
					if ( value.toString().match( /^(novalue|somevalue)$/ ) ) {
						values.push( {
							wd: { value: value },
							label: $( '<span>' )
								.append( $( '<span>' ).css( 'color', '#666' ).text( 'Значение ' ) )
								.append( $( '<strong>' ).text( value.toString() === 'novalue' ? 'отсутствует' : 'неизвестно' ) )
						} );
					} else {
						values.push( {
							wd: { value: value },
							label: $( '<span>' )
								.append( $( '<strong>' ).append( wdeFormatDataValue( {
									type: 'time',
									value: value
								} ) ) )
								.append( $( '<span>' ).css( 'color', '#666' ).text( ' (' +
									( value.calendarmodel.includes( '1985727' ) ? 'Григорианский' : 'Юлианский' ) + ') ' ) )
						} );
					}
				}
				break;

			case 'wikibase-item':
				value = wdeParseItems( $content, $wrapper, function ( values ) {
					wdeDialog( $field, propertyId, values, wdeGetReference( $content ) );
				} );
				return;

			case 'url':
				var $links = $content.find( 'a' );
				$links.each( function () {
					var $link = $( this );
					var url = $link.attr( 'href' ).replace( /^\/\//, 'https://' );
					values.push( {
						wd: { value: url },
						label: $( '<code>' + url + '</code>' )
					} );
				} );
				break;

			default:
				mw.notify( 'Неизвестный тип данных свойства: ' + datatype, {
					type: 'error',
					tag: 'wikidataInfoboxExport-error'
				} );
				console.log( datatype, $content ); // тут надо
		}

		values = $.unique( values );
		wdeDialog( $field, propertyId, values, wdeGetReference( $field ) );
	};

	/**
	 * Событие двойного клика по полю карточки
	 */
	var wdeClickEvent = function ( e ) {
		var $field = $( this );
		var propertyId = $field.attr( 'data-wikidata-property-id' );
		wdePrepareDialog( $field, propertyId );
	};

	/**
	 * Вывод диалога для подтверждения экспорта
	 */
	var wdeDialog = function ( $field, propertyId, values, refUrl ) {
		var fieldset;

		if ( !values || !values.length ) {
			mw.notify( 'Не удалось определить значение свойства', {
				type: 'error',
				tag: 'wikidataInfoboxExport-error'
			} );
			return;
		}

		// Создание диалога
		function ProcessDialog( config ) {
			ProcessDialog.super.call( this, config );
		}

		OO.inheritClass( ProcessDialog, OO.ui.ProcessDialog );
		ProcessDialog.static.name = 'Экспорт в Викиданные';
		ProcessDialog.static.title = $( '<span>' )
			.attr( 'title', 'Версия ' + wdeConfig.version )
			.text( ProcessDialog.static.name );
		ProcessDialog.static.actions = [
			{ action: 'export', label: 'Экспорт', flags: [ 'primary', 'constructive' ] },
			{ label: 'Отмена', flags: 'safe' }
		];
		ProcessDialog.prototype.initialize = function () {
			ProcessDialog.super.prototype.initialize.apply( this, arguments );
			this.content = new OO.ui.PanelLayout( { padded: true, expanded: false } );

			fieldset = new OO.ui.FieldsetLayout();
			var firstSelected = false;
			for ( var i = 0; i < values.length; i++ ) {
				var alreadyInWikidata = ( wdeAlreadyExistingItems[ propertyId ] || [] ).includes( ( ( values[ i ].wd || {} ).value || {} ).id );
				var checkbox = new OO.ui.CheckboxInputWidget( {
					value: JSON.stringify( values[ i ].wd ),
					selected: alreadyInWikidata,
					disabled: alreadyInWikidata
				} );
				if ( !checkbox.isDisabled() ) {
					if ( !firstSelected || !wdeConfig.properties[ propertyId ].constraints.unique ) {
						firstSelected = true;
						checkbox.setSelected( true );
					}

					if ( values[ i ].label[ 0 ].innerText.match( /уже используется/ ) &&
						wdeConfig.properties[ propertyId ].constraints.unique &&
						wdeConfig.properties[ propertyId ].datatype === 'external-id' ) {
						checkbox.setSelected( false );
					}
				}
				if ( refUrl ) {
					values[ i ].label.append( wdeFormatDomains( refUrl ) );
				}
				fieldset.addItems( [
					new OO.ui.FieldLayout( checkbox, {
						label: values[ i ].label,
						align: 'inline'
					} )
				] );
			}

			this.content.$element
				.append( $( '<p>' ).append( $( '<strong>' )
					.append( $( '<a>' ).attr( 'href', 'https://wikidata.org/wiki/Property:' + propertyId ).attr( 'target', '_blank' ).text( wdeConfig.properties[ propertyId ].label ) )
					.append( $( '<span>' ).text( ':' ) )
				) )
				.append( fieldset.$element )
				.append( $( '<hr>' ).css( 'margin-top', '1.5em' ) )
				.append( $( '<p>' ).text( 'Экспортировать значение свойства из карточки в Викиданные?' ) )
				.append( $( '<p>' ).css( 'font-size', 'smaller' ).html( 'Нажав на кнопку «Экспорт», вы соглашаетесь с <a href="https://foundation.wikimedia.org/wiki/Terms_of_Use" class="extiw" title="wikimedia:Terms of Use">условиями использования</a> и безотзывно соглашаетесь публиковать свой вклад на условиях <a rel="nofollow" class="external text" href="https://creativecommons.org/publicdomain/zero/1.0/">Creative Commons CC0 License</a>.' ) );

			this.$body.append( this.content.$element );
		};
		ProcessDialog.prototype.getActionProcess = function ( action ) {
			var dialog = this;
			if ( action === 'export' ) {
				return new OO.ui.Process( function () {
					var values = [];
					var fields = fieldset.getItems();
					for ( var i in fields ) {
						var checkbox = fields[ i ].getField();
						if ( checkbox.isSelected() && !checkbox.isDisabled() ) {
							values.push( checkbox.getValue() );
						}
					}

					wdeCreateClaims( propertyId, values, refUrl );
					dialog.close( { action: action } );
				}, this );
			}
			return ProcessDialog.super.prototype.getActionProcess.call( this, action );
		};
		var windowManager = new OO.ui.WindowManager();
		$( 'body' ).append( windowManager.$element );
		var processDialog = new ProcessDialog();
		windowManager.addWindows( [ processDialog ] );
		windowManager.openWindow( processDialog );
	};

	/**
	 * Инициализация гаджета
	 */
	var wdeInit = function () {
		if ( mw.config.get( 'wgWikibaseItemId' ) === null ||
			mw.config.get( 'wgAction' ) !== 'view' ||
			( window.ve && window.ve.init ) ||
			mw.config.get( 'wgNamespaceNumber' )
		) {
			return;
		}

		// Загрузка конфига из localStorage
		var storedConfig;
		try {
			storedConfig = JSON.parse( localStorage.getItem( 'wdeConfig' ) );
		} catch ( e ) {}
		if ( storedConfig && storedConfig.version == wdeConfig.version ) {
			wdeConfig = storedConfig;
		}
		if ( wdeConfig.properties === undefined ) {
			wdeConfig.properties = {};
		}

		// Установка сайта и языка пользователя
		wdeConfig.project = mw.config.get( 'wgDBname' );
		wdeConfig.language = mw.user.options.get( 'language' ) || mw.config.get( 'wgContentLanguage' );
		wdeConfig.languages.unshift( wdeConfig.language );
		wdeConfig.languages = $.unique( wdeConfig.languages );
		localStorage.setItem( 'wdeConfig', JSON.stringify( wdeConfig ) );

		// Инициализация API
		api = new mw.Api();
		wdApi = new mw.ForeignApi( '//www.wikidata.org/w/api.php' );

		// Инициализация диалогов
		wdeWindowManager = new OO.ui.WindowManager();
		$( 'body' ).append( wdeWindowManager.$element );

		// Запрос данных элемента
		wdApi.get( {
			action: 'wbgetentities',
			props: [ 'info', 'claims' ],
			ids: mw.config.get( 'wgWikibaseItemId' )
		} ).done( function ( data ) {
			if ( data.success ) {
				var claims;
				for ( var i in data.entities ) {
					if ( i == -1 ) {
						return;
					}

					claims = data.entities[ i ].claims;
					wdeBaseRevId = data.entities[ i ].lastrevid;
					break;
				}
				if ( !claims ) {
					return;
				}

				var $fields = $( '.infobox .no-wikidata' );
				$fields.each( function () {
					var $field = $( this );
					var propertyId = $field.attr( 'data-wikidata-property-id' );

					$field
						.removeClass( 'no-wikidata' )
						.off( 'dblclick' );
					wdePropertyIds.push( propertyId );
					wdeCanExportValue( $field, claims[ propertyId ], function ( hasClaims ) {
						$field.addClass( 'no-wikidata' );
						if ( hasClaims === true ) {
							$field.addClass( 'partial-wikidata' );
						}
						$field.on( 'dblclick', wdeClickEvent );
					} );
					
					var $fieldQualifiers = $field.closest( 'tr' ).find( '[data-wikidata-qualifier-id]' );
					$fieldQualifiers.each( function () {
						wdePropertyIds.push( $( this ).data( 'wikidata-qualifier-id' ) );
					} );
				} );
				mw.util.addCSS( '\
					.infobox .no-wikidata {\
						display: block !important;\
						background: #fdc;\
						padding: 5px 0;\
					}\
					.infobox .no-wikidata.partial-wikidata {\
						background: #eeb;\
					}\
					.infobox .no-wikidata .no-wikidata {\
						margin: -5px 0;\
					}\
					' );

				// TODO: Загружать при первом открытии окна
				wdeLoadProperties( wdePropertyIds );
			}
		} );
	};

	$.when(
		$.ready,
		mw.loader.using( [
			'mediawiki.api',
			'mediawiki.ForeignApi',
			'mediawiki.util',
			'oojs-ui-core',
			'oojs-ui-widgets',
			'oojs-ui-windows'
		] )
	).done( wdeInit );
}( mediaWiki, jQuery ) );