public async Task SiteInfoTest() { var site = await WikidataSiteAsync; var info = WikibaseSiteInfo.FromSiteInfo(site.SiteInfo); Assert.Equal("http://www.wikidata.org/entity/", info.ConceptBaseUri); Assert.Equal("https://commons.wikimedia.org/wiki/", info.GeoShapeStorageBaseUri); Assert.Equal("https://commons.wikimedia.org/wiki/", info.TabularDataStorageBaseUri); Assert.Equal("http://www.wikidata.org/entity/Q50", info.MakeEntityUri("Q50")); Assert.Equal("Q123", info.ParseEntityId("http://www.wikidata.org/entity/Q123")); }
/// <inheritdoc /> protected override async Task ProcessRecordAsync(CancellationToken cancellationToken) { var options = EntityEditOptions.Bulk; if (Bot) { options |= EntityEditOptions.Bot; } var entity = new Entity(SourceSite, SourceId); await entity.RefreshAsync(EntityQueryOptions.FetchClaims | EntityQueryOptions.FetchInfo, null, cancellationToken); if (!entity.Exists) { throw new InvalidOperationException($"The source entity {entity} does not exist."); } var dest = new Entity(DestinationSite, DestinationId); await dest.RefreshAsync(EntityQueryOptions.FetchInfo | EntityQueryOptions.FetchClaims); if (!dest.Exists) { throw new InvalidOperationException($"The destination entity {dest} does not exist."); } sourceSiteWikibaseInfo = WikibaseSiteInfo.FromSiteInfo(SourceSite.SiteInfo); destinationSiteWikibaseInfo = WikibaseSiteInfo.FromSiteInfo(DestinationSite.SiteInfo); var refProp = CitationProperty == null ? null : new Entity(DestinationSite, CitationProperty); // Lookup for the claims that are previously imported from wikidata // Claim ID --> Claim Dictionary <string, Claim> existingClaims = null; if (refProp != null) { await refProp.RefreshAsync(EntityQueryOptions.FetchInfo); existingClaims = dest.Claims.Select(c => new { Id = c.References.Select(r => (string)r.Snaks.FirstOrDefault(s => s.PropertyId == refProp.Id)?.DataValue) .FirstOrDefault(s => s != null), Claim = c }).Where(t => t.Id != null) .ToDictionary(t => t.Id, t => t.Claim); } var newClaims = new List <Claim>(); var optionalProp = OptionalProperty == null ? null : new HashSet <string>(OptionalProperty); bool IsPropertyValueOptional(string propertyId) { return(optionalProp?.Contains(propertyId) ?? false); } IEnumerable <string> props = Property; var newClaimsCounter = 0; var updatedClaimsCounter = 0; if (Property.Length == 1 && Property[0] == "*") { if (EntityMapping == null) { throw new ArgumentNullException(nameof(EntityMapping)); } props = EntityMapping.Keys.Cast <string>(); } foreach (var prop in props) { if (prop == null) { throw new ArgumentException("Properties have null item.", nameof(Property)); } var pc = entity.Claims[prop.Trim().ToUpperInvariant()]; foreach (var claim in pc) { if (existingClaims != null && existingClaims.TryGetValue(claim.Id, out var newClaim)) { var updated = false; if (!SnakValueEquals(claim.MainSnak, newClaim.MainSnak, IsPropertyValueOptional(claim.MainSnak.PropertyId))) { newClaim.MainSnak.SnakType = claim.MainSnak.SnakType; newClaim.MainSnak.RawDataValue = claim.MainSnak.RawDataValue; FixValueEntityReference(newClaim.MainSnak, IsPropertyValueOptional(claim.MainSnak.PropertyId)); updated = true; } var qualifiers = new List <Snak>(); var reusedQs = 0; var newQs = 0; foreach (var q in claim.Qualifiers) { var mapped = MapEntity(q.PropertyId, true); if (mapped == null) { continue; } var newQ = newClaim.Qualifiers.FirstOrDefault(ourQ => ourQ.PropertyId == mapped && SnakValueEquals(q, ourQ, IsPropertyValueOptional(q.PropertyId))); if (newQ == null) { newQs++; newQ = new Snak(mapped) { DataType = q.DataType, RawDataValue = q.RawDataValue }; FixValueEntityReference(newQ, optionalProp?.Contains(q.PropertyId) ?? false); } else { reusedQs++; } qualifiers.Add(newQ); } if (newQs > 0 || reusedQs < newClaim.Qualifiers.Count) { newClaim.Qualifiers.Clear(); newClaim.Qualifiers.AddRange(qualifiers); updated = true; } if (!updated) { continue; } updatedClaimsCounter++; } else { newClaimsCounter++; newClaim = new Claim(CloneSnak(claim.MainSnak, IsPropertyValueOptional(claim.MainSnak.PropertyId))); foreach (var qualifier in claim.Qualifiers) { var snak = CloneSnak(qualifier, IsPropertyValueOptional(qualifier.PropertyId)); if (snak == null) { continue; } newClaim.Qualifiers.Add(snak); } } if (refProp != null) { if (newClaim.References.All(r => (string)r.Snaks.FirstOrDefault(s => s.PropertyId == refProp.Id)?.DataValue != claim.Id)) { var refSnak = new Snak(refProp.Id, claim.Id, refProp.DataType); newClaim.References.Add(new ClaimReference(refSnak)); } } newClaims.Add(newClaim); } } var changes = new List <EntityEditEntry>(); changes.AddRange(newClaims.Select(c => new EntityEditEntry(nameof(dest.Claims), c))); if (changes.Count == 0) { WriteCommandDetail($"No matching claims to copy from \"{entity.Id}\"({SourceSite}) to {dest.Id}({DestinationSite})"); return; } if (!ShouldProcess(string.Format("{0}({1}) --> {2}({3}), {4} changes. Claims: +{5}, !{6}", entity.Id, entity.Site, dest.Id, dest.Site, changes.Count, newClaimsCounter, updatedClaimsCounter))) { return; } if (Progressive) { int counter = 1; foreach (var change in changes) { var claim = (Claim)change.Value; if (ShouldProcess(string.Format("{0}({1}) --> {2}({3}), Target claim: {4}", entity.Id, entity.Site, dest.Id, dest.Site, claim))) { await dest.EditAsync(new[] { change }, $"[{counter}/{changes.Count}] Adding {newClaimsCounter} / updating {updatedClaimsCounter} claims from \"{entity.Id}\" on {SourceSite}.", options, cancellationToken); } } } else { await dest.EditAsync(changes, $"Added {newClaimsCounter} / updated {updatedClaimsCounter} claims from \"{entity.Id}\" on {SourceSite}.", options, cancellationToken); } WriteCommandDetail($"Added {newClaimsCounter} / updated {updatedClaimsCounter} claims from \"{entity.Id}\"({SourceSite}) to {dest.Id}({DestinationSite})"); }