Skip to content

Commit fc0d269

Browse files
committed
Merge branch 'develop'
2 parents 48f677b + 1a5c27c commit fc0d269

24 files changed

+443
-73
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
language: csharp
22
mono: none
33
sudo: false
4-
dotnet: 2.2
4+
dotnet: 3.1
55
dist: xenial
66

77

doc/release-notes.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
# NetTopologySuite.IO.GPX Release Notes
22

3-
## 1.0.0
3+
## [1.1.0](https://github.com/NetTopologySuite/NetTopologySuite.IO.GPX/milestone/11)
4+
- Allow specifying arbitrary namespaces (+prefixes) on the root element so that they don't have to be repeated on every `extensions` element ([#36](https://github.com/NetTopologySuite/NetTopologySuite.IO.GPX/issues/36)).
5+
- Add opt-in support in `GpxWebLink` for URI strings longer than 65519 characters, as a workaround for dotnet/runtime#1875 ([#39](https://github.com/NetTopologySuite/NetTopologySuite.IO.GPX/issues/39)).
6+
- If upstream relaxes this restriction in a later release, then this code will automatically "upgrade" to fully support the new higher restriction.
7+
8+
## [1.0.0](https://github.com/NetTopologySuite/NetTopologySuite.IO.GPX/milestone/6)
49
- Updates to make this compatible with v2 of the core NTS library.
510

611
## 0.6.0

generate-docs.cmd

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ REM ===========================================================================
33
REM Regenerates the https://nettopologysuite.github.io/NetTopologySuite.IO.GPX
44
REM content locally
55
REM ===========================================================================
6-
set DOCFX_PACKAGE_VERSION=2.43.1
6+
set DOCFX_PACKAGE_VERSION=2.48.1
77
pushd %~dp0
88
REM incremental / cached builds tweak things about the output, so let's do it
99
REM all fresh if we can help it...

src/Directory.Build.props

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
<PropertyGroup Label="Version numbers">
3939
<!-- MAJOR, MINOR, and PATCH are defined according to SemVer 2.0.0. -->
4040
<NtsMajorVersion>1</NtsMajorVersion>
41-
<NtsMinorVersion>0</NtsMinorVersion>
41+
<NtsMinorVersion>1</NtsMinorVersion>
4242
<NtsPatchVersion>0</NtsPatchVersion>
4343

4444
<NtsBuildTimestamp>$([System.DateTime]::UtcNow.Ticks)</NtsBuildTimestamp>
@@ -92,7 +92,7 @@
9292

9393
<ItemGroup>
9494
<!-- SourceLink adds stuff to let debuggers step into our code. -->
95-
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0-beta2-19367-01" PrivateAssets="All" />
95+
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
9696
</ItemGroup>
9797

9898
</Project>

src/NetTopologySuite.IO.GPX/GpxCopyright.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ internal static GpxCopyright Load(XElement element)
148148
return new GpxCopyright(
149149
author: element.Attribute("author")?.Value ?? throw new XmlException("copyright element must have author attribute."),
150150
year: Helpers.ParseGregorianYear(element.GpxElement("year")?.Value),
151-
licenseUri: Helpers.ParseUri(element.GpxElement("license")?.Value));
151+
licenseUri: Helpers.ParseUri(element.GpxElement("license")?.Value)); // assumption: overlong URIs don't really make sense here, so we can ignore the issue here
152152
}
153153

154154
void ICanWriteToXmlWriter.Save(XmlWriter writer)

src/NetTopologySuite.IO.GPX/GpxMetadata.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -399,9 +399,9 @@ internal static GpxMetadata Load(XElement element, GpxReaderSettings settings, s
399399
creator: creator,
400400
name: element.GpxElement("name")?.Value,
401401
description: element.GpxElement("desc")?.Value,
402-
author: GpxPerson.Load(element.GpxElement("author")),
402+
author: GpxPerson.Load(element.GpxElement("author"), settings.BuildWebLinksForVeryLongUriValues),
403403
copyright: GpxCopyright.Load(element.GpxElement("copyright")),
404-
links: ImmutableArray.CreateRange(element.GpxElements("link").Select(GpxWebLink.Load)),
404+
links: ImmutableArray.CreateRange(element.GpxElements("link").Select(el => GpxWebLink.Load(el, settings.BuildWebLinksForVeryLongUriValues))),
405405
creationTimeUtc: Helpers.ParseDateTimeUtc(element.GpxElement("time")?.Value, settings.TimeZoneInfo, settings.IgnoreBadDateTime),
406406
keywords: element.GpxElement("keywords")?.Value,
407407
bounds: GpxBoundingBox.Load(element.GpxElement("bounds")),

src/NetTopologySuite.IO.GPX/GpxPerson.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ public override string ToString() => Helpers.BuildString((nameof(Name), Name),
114114
(nameof(Email), Email),
115115
(nameof(Link), Link));
116116

117-
internal static GpxPerson Load(XElement element)
117+
internal static GpxPerson Load(XElement element, bool allowOverlongDataUri)
118118
{
119119
if (element is null)
120120
{
@@ -124,7 +124,7 @@ internal static GpxPerson Load(XElement element)
124124
return new GpxPerson(
125125
name: element.GpxElement("name")?.Value,
126126
email: GpxEmail.Load(element.GpxElement("email")),
127-
link: GpxWebLink.Load(element.GpxElement("link")));
127+
link: GpxWebLink.Load(element.GpxElement("link"), allowOverlongDataUri));
128128
}
129129

130130
void ICanWriteToXmlWriter.Save(XmlWriter writer)

src/NetTopologySuite.IO.GPX/GpxReaderSettings.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,5 +55,15 @@ public TimeZoneInfo TimeZoneInfo
5555
/// NetTopologySuite/NetTopologySuite.IO.GPX#29).
5656
/// </summary>
5757
public bool IgnoreBadDateTime { get; set; }
58+
59+
/// <summary>
60+
/// Gets or sets a value indicating whether or not to build <see cref="GpxWebLink"/> objects
61+
/// for strings whose lengths are greater than or equal to 65520 characters. Older versions
62+
/// of this library would throw exceptions for such strings (see dotnet/runtime#1857 and
63+
/// NetTopologySuite/NetTopologySuite.IO.GPX#39), and the workaround involves relaxing an
64+
/// invariant of <see cref="GpxWebLink"/>, so it is guarded by an opt-in flag to avoid a
65+
/// breaking change.
66+
/// </summary>
67+
public bool BuildWebLinksForVeryLongUriValues { get; set; }
5868
}
5969
}

src/NetTopologySuite.IO.GPX/GpxRoute.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ internal static GpxRoute Load(XElement element, GpxReaderSettings settings)
309309
comment: element.GpxElement("cmt")?.Value,
310310
description: element.GpxElement("desc")?.Value,
311311
source: element.GpxElement("src")?.Value,
312-
links: ImmutableArray.CreateRange(element.GpxElements("link").Select(GpxWebLink.Load)),
312+
links: ImmutableArray.CreateRange(element.GpxElements("link").Select(el => GpxWebLink.Load(el, settings.BuildWebLinksForVeryLongUriValues))),
313313
number: Helpers.ParseUInt32(element.GpxElement("number")?.Value),
314314
classification: element.GpxElement("type")?.Value,
315315
extensions: extensionsElement is null ? null : settings.ExtensionReader.ConvertRouteExtensionElement(extensionsElement.Elements()),

src/NetTopologySuite.IO.GPX/GpxTrack.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ internal static GpxTrack Load(XElement element, GpxReaderSettings settings)
306306
comment: element.GpxElement("cmt")?.Value,
307307
description: element.GpxElement("desc")?.Value,
308308
source: element.GpxElement("src")?.Value,
309-
links: ImmutableArray.CreateRange(element.GpxElements("link").Select(GpxWebLink.Load)),
309+
links: ImmutableArray.CreateRange(element.GpxElements("link").Select(el => GpxWebLink.Load(el, settings.BuildWebLinksForVeryLongUriValues))),
310310
number: Helpers.ParseUInt32(element.GpxElement("number")?.Value),
311311
classification: element.GpxElement("type")?.Value,
312312
extensions: extensionsElement is null ? null : settings.ExtensionReader.ConvertTrackExtensionElement(extensionsElement.Elements()),

src/NetTopologySuite.IO.GPX/GpxWaypoint.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -732,7 +732,7 @@ internal static GpxWaypoint Load(XElement element, GpxReaderSettings settings, F
732732
comment: element.GpxElement("cmt")?.Value,
733733
description: element.GpxElement("desc")?.Value,
734734
source: element.GpxElement("src")?.Value,
735-
links: ImmutableArray.CreateRange(element.GpxElements("link").Select(GpxWebLink.Load)),
735+
links: ImmutableArray.CreateRange(element.GpxElements("link").Select(el => GpxWebLink.Load(el, settings.BuildWebLinksForVeryLongUriValues))),
736736
symbolText: element.GpxElement("sym")?.Value,
737737
classification: element.GpxElement("type")?.Value,
738738
fixKind: Helpers.ParseFixKind(element.GpxElement("fix")?.Value),

src/NetTopologySuite.IO.GPX/GpxWebLink.cs

Lines changed: 123 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,65 @@ public GpxWebLink(Uri href)
4545
public GpxWebLink(Uri href, string text, string contentType)
4646
{
4747
Href = href ?? throw new ArgumentNullException(nameof(href));
48+
HrefString = href.OriginalString;
4849
Text = text;
4950
ContentType = contentType;
5051
}
5152

53+
/// <summary>
54+
/// Initializes a new instance of the <see cref="GpxWebLink"/> class.
55+
/// <para>
56+
/// If <paramref name="hrefString"/> is never longer than 65519 characters, then favor using
57+
/// <see cref="GpxWebLink(Uri, string, string)"/> instead of this, since <see cref="Href"/>
58+
/// will never be <see langword="null"/> when using that constructor.
59+
/// </para>
60+
/// </summary>
61+
/// <param name="hrefString">
62+
/// The value of <see cref="HrefString"/>.
63+
/// </param>
64+
/// <param name="text">
65+
/// The value of <see cref="Text"/>.
66+
/// </param>
67+
/// <param name="contentType">
68+
/// The value of <see cref="ContentType"/>.
69+
/// </param>
70+
/// <exception cref="ArgumentNullException">
71+
/// <paramref name="hrefString"/> is <see langword="null"/>.
72+
/// </exception>
73+
/// <exception cref="ArgumentException">
74+
/// <paramref name="hrefString"/> does not look like a valid URI string.
75+
/// </exception>
76+
public GpxWebLink(string hrefString, string text, string contentType)
77+
{
78+
switch (Helpers.InterpretUri(hrefString, out var bestEffortHrefUri))
79+
{
80+
case UriValidationResult.NullValue:
81+
throw new ArgumentNullException(nameof(hrefString));
82+
83+
case UriValidationResult.ValidSystemUri:
84+
Href = bestEffortHrefUri;
85+
break;
86+
87+
case UriValidationResult.ValidOverlongDataUri:
88+
break;
89+
90+
default:
91+
throw new ArgumentException("does not look like a valid URI string", nameof(hrefString));
92+
}
93+
94+
HrefString = hrefString;
95+
Text = text;
96+
ContentType = contentType;
97+
}
98+
99+
private GpxWebLink(string hrefString, string text, string contentType, Uri bestEffortHrefUri)
100+
{
101+
HrefString = hrefString;
102+
Text = text;
103+
ContentType = contentType;
104+
Href = bestEffortHrefUri;
105+
}
106+
52107
/// <summary>
53108
/// Gets the text of the hyperlink.
54109
/// </summary>
@@ -66,24 +121,33 @@ public GpxWebLink(Uri href, string text, string contentType)
66121
public string ContentType { get; }
67122

68123
/// <summary>
69-
/// Gets the URL of the hyperlink.
124+
/// Gets the URL of the hyperlink, or <see langword="null"/> if this instance was created
125+
/// from a valid-looking data URI whose total length exceeded 65519 characters.
70126
/// </summary>
71127
/// <remarks>
72128
/// In the official XSD schema for GPX 1.1, this corresponds to the "href" attribute.
73129
/// </remarks>
74130
public Uri Href { get; }
75131

132+
/// <summary>
133+
/// Gets the URL of the hyperlink, as a string.
134+
/// </summary>
135+
/// <remarks>
136+
/// In the official XSD schema for GPX 1.1, this corresponds to the "href" attribute.
137+
/// </remarks>
138+
public string HrefString { get; }
139+
76140
/// <inheritdoc />
77141
public override bool Equals(object obj) => obj is GpxWebLink other &&
78142
Text == other.Text &&
79143
ContentType == other.ContentType &&
80-
Href == other.Href;
144+
HrefString == other.HrefString;
81145

82146
/// <inheritdoc />
83-
public override int GetHashCode() => (Href, Text, ContentType).GetHashCode();
147+
public override int GetHashCode() => (HrefString, Text, ContentType).GetHashCode();
84148

85149
/// <inheritdoc />
86-
public override string ToString() => Helpers.BuildString((nameof(Href), Href),
150+
public override string ToString() => Helpers.BuildString((nameof(HrefString), HrefString),
87151
(nameof(Text), Text),
88152
(nameof(ContentType), ContentType));
89153

@@ -115,36 +179,82 @@ public override string ToString() => Helpers.BuildString((nameof(Href), Href),
115179

116180
/// <summary>
117181
/// Builds a new instance of <see cref="GpxWebLink"/> as a copy of this instance, but with
118-
/// <see cref="Href"/> replaced by the given value.
182+
/// <see cref="Href"/> and <see cref="HrefString"/> replaced by the given value.
119183
/// </summary>
120184
/// <param name="href">
121-
/// The new value for <see cref="Href"/>.
185+
/// The new value for <see cref="Href"/> and <see cref="HrefString"/>.
122186
/// </param>
123187
/// <returns>
124188
/// A new <see cref="GpxWebLink"/> instance that's a copy of the current instance, but
125-
/// with its <see cref="Href"/> value set to <paramref name="href"/>.
189+
/// with its <see cref="Href"/> value set to <paramref name="href"/> and with its
190+
/// <see cref="HrefString"/> set accordingly.
126191
/// </returns>
127192
/// <exception cref="ArgumentNullException">
128193
/// <paramref name="href"/> is <see langword="null"/>.
129194
/// </exception>
130195
public GpxWebLink WithHref(Uri href) => new GpxWebLink(href, Text, ContentType);
131196

132-
internal static GpxWebLink Load(XElement element)
197+
/// <summary>
198+
/// Builds a new instance of <see cref="GpxWebLink"/> as a copy of this instance, but with
199+
/// <see cref="HrefString"/> and <see cref="Href"/> replaced according to the given value.
200+
/// </summary>
201+
/// <param name="hrefString">
202+
/// The new value for <see cref="HrefString"/> and (if possible) <see cref="Href"/>.
203+
/// </param>
204+
/// <returns>
205+
/// A new <see cref="GpxWebLink"/> instance that's a copy of the current instance, but
206+
/// with its <see cref="HrefString"/> value set to <paramref name="hrefString"/> and its
207+
/// <see cref="Href"/> value set accordingly.
208+
/// </returns>
209+
/// <exception cref="ArgumentNullException">
210+
/// <paramref name="hrefString"/> is <see langword="null"/>.
211+
/// </exception>
212+
/// <exception cref="ArgumentException">
213+
/// <paramref name="hrefString"/> does not look like a valid URI string.
214+
/// </exception>
215+
public GpxWebLink WithHrefString(string hrefString) => new GpxWebLink(hrefString, Text, ContentType);
216+
217+
internal static GpxWebLink Load(XElement element, bool allowOverlongDataUri)
133218
{
134219
if (element is null)
135220
{
136221
return null;
137222
}
138223

139-
return new GpxWebLink(
140-
href: Helpers.ParseUri(element.Attribute("href")?.Value) ?? throw new XmlException("link element must have 'href' attribute"),
141-
text: element.GpxElement("text")?.Value,
142-
contentType: element.GpxElement("type")?.Value);
224+
string hrefString = element.Attribute("href")?.Value;
225+
string text = element.GpxElement("text")?.Value;
226+
string contentType = element.GpxElement("type")?.Value;
227+
switch (Helpers.InterpretUri(hrefString, out var bestEffortHrefUri))
228+
{
229+
case UriValidationResult.NullValue:
230+
throw new XmlException("link element must have 'href' attribute");
231+
232+
case UriValidationResult.ValidSystemUri:
233+
return new GpxWebLink(
234+
href: bestEffortHrefUri,
235+
text: text,
236+
contentType: contentType);
237+
238+
case UriValidationResult.ValidOverlongDataUri:
239+
if (!allowOverlongDataUri)
240+
{
241+
throw new XmlException($"link element's 'href' attribute looks like a valid (but long) data URI. GpxWebLink.Href will be null in these cases, so to allow this, you will need to set the {nameof(GpxReaderSettings.BuildWebLinksForVeryLongUriValues)} flag.");
242+
}
243+
244+
return new GpxWebLink(
245+
hrefString: hrefString,
246+
text: text,
247+
contentType: contentType,
248+
bestEffortHrefUri: bestEffortHrefUri);
249+
250+
default:
251+
throw new XmlException("link element's 'href' attribute does not look like a valid URI");
252+
}
143253
}
144254

145255
void ICanWriteToXmlWriter.Save(XmlWriter writer)
146256
{
147-
writer.WriteAttributeString("href", Href.OriginalString);
257+
writer.WriteAttributeString("href", HrefString);
148258
writer.WriteOptionalGpxElementValue("text", Text);
149259
writer.WriteOptionalGpxElementValue("type", ContentType);
150260
}

src/NetTopologySuite.IO.GPX/GpxWriter.cs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,19 +76,19 @@ public static void Write(XmlWriter writer, GpxWriterSettings settings, GpxMetada
7676
/// <list type="table">
7777
/// <listheader>
7878
/// <term>Type of <see cref="IFeature.Geometry"/></term>
79-
/// <term>Corresponding top-level data object</term>
79+
/// <description>Corresponding top-level data object</description>
8080
/// </listheader>
8181
/// <item>
8282
/// <term><see cref="Point"/></term>
83-
/// <term><see cref="GpxWaypoint"/></term>
83+
/// <description><see cref="GpxWaypoint"/></description>
8484
/// </item>
8585
/// <item>
8686
/// <term><see cref="LineString"/></term>
87-
/// <term><see cref="GpxRoute"/></term>
87+
/// <description><see cref="GpxRoute"/></description>
8888
/// </item>
8989
/// <item>
9090
/// <term><see cref="MultiLineString"/></term>
91-
/// <term><see cref="GpxTrack"/></term>
91+
/// <description><see cref="GpxTrack"/></description>
9292
/// </item>
9393
/// </list>
9494
/// </remarks>
@@ -202,6 +202,12 @@ public static void Write(XmlWriter writer, GpxWriterSettings settings, GpxMetada
202202
writer.WriteGpxStartElement("gpx");
203203
writer.WriteAttributeString("version", "1.1");
204204
writer.WriteAttributeString("creator", metadata.Creator);
205+
206+
foreach (var (desiredPrefix, namespaceUri) in settings.CommonXmlNamespacesByDesiredPrefix)
207+
{
208+
writer.WriteAttributeString("xmlns", desiredPrefix, null, namespaceUri.ToString());
209+
}
210+
205211
if (!metadata.IsTrivial)
206212
{
207213
writer.WriteGpxStartElement("metadata");

src/NetTopologySuite.IO.GPX/GpxWriterSettings.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
using System;
2+
using System.Collections.Generic;
3+
using System.Xml;
24

35
namespace NetTopologySuite.IO
46
{
@@ -35,5 +37,18 @@ public TimeZoneInfo TimeZoneInfo
3537
/// is an instance of the base class (see its summary documentation for details).
3638
/// </summary>
3739
public GpxExtensionWriter ExtensionWriter { get; set; } = DefaultExtensionWriter;
40+
41+
/// <summary>
42+
/// Gets an <see cref="IDictionary{TKey, TValue}"/> instance that can be used to register
43+
/// additional namespaces to declare on the root element, keyed by the desired prefix to use
44+
/// in the result. These can be used to avoid duplicating the namespace declaration for
45+
/// every single "extensions" element that uses the exact same namespace.
46+
/// <para>
47+
/// The <see cref="XmlWriterSettings"/>'s <see cref="XmlWriterSettings.NamespaceHandling"/>
48+
/// value will need to be set to <see cref="NamespaceHandling.OmitDuplicates"/> when the
49+
/// writer is created in order to fully benefit from using this property.
50+
/// </para>
51+
/// </summary>
52+
public IDictionary<string, Uri> CommonXmlNamespacesByDesiredPrefix { get; } = new Dictionary<string, Uri>();
3853
}
3954
}

0 commit comments

Comments
 (0)