Skip to content

Commit b943560

Browse files
committed
Merge branch 'develop'
2 parents fc0d269 + 7b6b7e9 commit b943560

File tree

5 files changed

+139
-6
lines changed

5 files changed

+139
-6
lines changed

doc/release-notes.md

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

3+
## [1.1.1](https://github.com/NetTopologySuite/NetTopologySuite.IO.GPX/milestone/13)
4+
- `GpxReader` will now throw an `XmlException` when it encounters an unexpected top-level child element of the root `gpx` element, instead of running into an infinite loop ([#41](https://github.com/NetTopologySuite/NetTopologySuite.IO.GPX/issues/41)).
5+
- "Unexpected top-level child elements" are elements whose names we do not recognize, `metadata` or `extensions` showing up more than once, or the `metadata` element showing up somewhere *after* the first child element.
6+
- `GpxReaderSettings` now has an `IgnoreUnexpectedChildrenOfTopLevelElement` property to use to ignore those elements instead of throwing `XmlException`.
7+
38
## [1.1.0](https://github.com/NetTopologySuite/NetTopologySuite.IO.GPX/milestone/11)
49
- 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)).
510
- 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)).

src/Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
<!-- MAJOR, MINOR, and PATCH are defined according to SemVer 2.0.0. -->
4040
<NtsMajorVersion>1</NtsMajorVersion>
4141
<NtsMinorVersion>1</NtsMinorVersion>
42-
<NtsPatchVersion>0</NtsPatchVersion>
42+
<NtsPatchVersion>1</NtsPatchVersion>
4343

4444
<NtsBuildTimestamp>$([System.DateTime]::UtcNow.Ticks)</NtsBuildTimestamp>
4545
<NtsDaysSinceEpoch>$([System.DateTime]::op_Subtraction($([System.DateTime]::new($(NtsBuildTimestamp)).Date),$([System.DateTime]::new(621355968000000000))).TotalDays.ToString("00000"))</NtsDaysSinceEpoch>

src/NetTopologySuite.IO.GPX/GpxReader.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,10 @@ public static void Read(XmlReader reader, GpxReaderSettings settings, GpxVisitor
142142
if (reader.Name == "metadata")
143143
{
144144
ReadMetadata(reader, settings, creator, visitor);
145+
if (!ReadTo(reader, XmlNodeType.Element, XmlNodeType.EndElement))
146+
{
147+
break;
148+
}
145149
}
146150
else
147151
{
@@ -175,6 +179,13 @@ public static void Read(XmlReader reader, GpxReaderSettings settings, GpxVisitor
175179
}
176180

177181
break;
182+
183+
case string _ when settings.IgnoreUnexpectedChildrenOfTopLevelElement:
184+
reader.Skip();
185+
break;
186+
187+
default:
188+
throw new XmlException($"Unexpected xml node '{reader.Name}'");
178189
}
179190
}
180191
while (ReadTo(reader, XmlNodeType.Element, XmlNodeType.EndElement));

src/NetTopologySuite.IO.GPX/GpxReaderSettings.cs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ public sealed class GpxReaderSettings
1515
/// <summary>
1616
/// Gets or sets the <see cref="System.TimeZoneInfo"/> instance that the system should use
1717
/// to interpret timestamps in the GPX file, when the file itself does not contain time zone
18-
/// information. Default is <see cref="TimeZoneInfo.Utc"/>.
18+
/// information. Default is <see cref="System.TimeZoneInfo.Utc"/>.
1919
/// <para>
20-
/// <see langword="null"/> is treated as <see cref="TimeZoneInfo.Utc"/>, but please prefer
21-
/// <see langword="null"/> so that <see cref="TimeZoneInfo.ClearCachedData"/> does not
22-
/// affect our correctness.
20+
/// <see langword="null"/> is treated as <see cref="System.TimeZoneInfo.Utc"/>, but please
21+
/// prefer <see langword="null"/> so that <see cref="System.TimeZoneInfo.ClearCachedData"/>
22+
/// does not affect our correctness.
2323
/// </para>
2424
/// </summary>
2525
public TimeZoneInfo TimeZoneInfo
@@ -65,5 +65,20 @@ public TimeZoneInfo TimeZoneInfo
6565
/// breaking change.
6666
/// </summary>
6767
public bool BuildWebLinksForVeryLongUriValues { get; set; }
68+
69+
/// <summary>
70+
/// Gets or sets a value indicating whether or not to ignore child elements of the top-level
71+
/// <c>&lt;gpx&gt;</c>element that are not expected to appear where they do, even though
72+
/// such files would not pass XSD validation (see
73+
/// NetTopologySuite/NetTopologySuite.IO.GPX#41).
74+
/// <para>
75+
/// This covers elements whose names are completely unrecognized, but also:
76+
/// <list type="bullet">
77+
/// <item><description><c>&lt;metadata&gt;</c> somewhere other than the first child element of <c>&lt;gpx&gt;</c></description></item>
78+
/// <item><description><c>&lt;metadata&gt;</c> or <c>&lt;extensions&gt;</c> appearing more than once</description></item>
79+
/// </list>
80+
/// </para>
81+
/// </summary>
82+
public bool IgnoreUnexpectedChildrenOfTopLevelElement { get; set; }
6883
}
6984
}

tests/NetTopologySuite.IO.GPX.Tests/GpxFileTests.cs

Lines changed: 103 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.Diagnostics;
34
using System.IO;
45
using System.Linq;
@@ -344,7 +345,7 @@ public void TimestampsShouldPreserveFractionalSecondsWithinDefinedPrecision()
344345
</wpt>
345346
</gpx>
346347
";
347-
string text = GpxFile.Parse(GpxText, null). BuildString(null);
348+
string text = GpxFile.Parse(GpxText, null).BuildString(null);
348349

349350
Assert.Contains("1234-05-06T07:08:09.7654321Z", text);
350351
Assert.Contains("5432-10-10T11:22:33.8765432Z", text); // DateTime resolution is 100ns, so the value gets rounded to 7 digits
@@ -502,5 +503,106 @@ public void OverlongDataUrisShouldBeAccepted_OptIn(int totalUriLength)
502503
string text = file.BuildString(null);
503504
Assert.Contains(uriText, text);
504505
}
506+
507+
[Fact]
508+
[Regression]
509+
[GitHubIssue(41)]
510+
public void ChildElementWithUnexpectedNameShouldBeIgnored_OptIn()
511+
{
512+
const string GpxText = @"
513+
<gpx version='1.1' creator='S Health_0.2' n0:schemaLocation='http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd' xmlns='http://www.topografix.com/GPX/1/1' n1:xsi='http://www.w3.org/2001/XMLSchema-instance' n1:gpx1='http://www.topografix.com/GPX/1/0' n1:ogt10='http://gpstracker.android.sogeti.n1/GPX/1/0' xmlns:n0='xsi' xmlns:n1='xmlns'>
514+
<metadate>2020-07-31T03:01:31Z</metadate>
515+
<trk>
516+
<name>20200731_090010.gpx</name>
517+
<trkseg>
518+
<trkpt lat='32.737328' lon='35.65718'>
519+
<ele>346.0538</ele>
520+
<time>2020-07-31T03:01:31Z</time>
521+
</trkpt>
522+
</trkseg>
523+
</trk>
524+
<exerciseinfo>
525+
<exercisetype>11007</exercisetype>
526+
</exerciseinfo>
527+
</gpx>
528+
";
529+
Assert.ThrowsAny<XmlException>(() => GpxFile.Parse(GpxText, null));
530+
531+
var gpx = GpxFile.Parse(GpxText, new GpxReaderSettings { IgnoreUnexpectedChildrenOfTopLevelElement = true });
532+
var trk = Assert.Single(gpx.Tracks);
533+
var trkseg = Assert.Single(trk.Segments);
534+
var trkpt = Assert.Single(trkseg.Waypoints);
535+
Assert.Equal(new GpxLatitude(32.737328), trkpt.Latitude);
536+
Assert.Equal(new GpxLongitude(35.65718), trkpt.Longitude);
537+
}
538+
539+
[Fact]
540+
[Regression]
541+
[GitHubIssue(41)]
542+
public void MetadataOutOfOrderShouldBeIgnored_OptIn()
543+
{
544+
const string GpxText = @"
545+
<gpx version='1.1' creator='HarelM' n0:schemaLocation='http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd' xmlns='http://www.topografix.com/GPX/1/1' n1:xsi='http://www.w3.org/2001/XMLSchema-instance' n1:gpx1='http://www.topografix.com/GPX/1/0' n1:ogt10='http://gpstracker.android.sogeti.n1/GPX/1/0' xmlns:n0='xsi' xmlns:n1='xmlns'>
546+
<trk>
547+
<name>20200731_090010.gpx</name>
548+
<trkseg>
549+
<trkpt lat='32.737328' lon='35.65718'>
550+
<ele>346.0538</ele>
551+
<time>2020-07-31T03:01:31Z</time>
552+
</trkpt>
553+
</trkseg>
554+
</trk>
555+
<metadata>
556+
<link href='somelink.com' />
557+
</metadata>
558+
</gpx>
559+
";
560+
Assert.ThrowsAny<XmlException>(() => GpxFile.Parse(GpxText, null));
561+
562+
var gpx = GpxFile.Parse(GpxText, new GpxReaderSettings { IgnoreUnexpectedChildrenOfTopLevelElement = true });
563+
Assert.Equal("HarelM", gpx.Metadata.Creator);
564+
Assert.True(gpx.Metadata.IsTrivial); // metadata element came too late
565+
}
566+
567+
[Fact]
568+
[Regression]
569+
[GitHubIssue(41)]
570+
public void ExtraMetadataOrExtensionsShouldBeIgnored_OptIn()
571+
{
572+
const string GpxText = @"
573+
<gpx xmlns='http://www.topografix.com/GPX/1/1' version='1.1' creator='airbreather'>
574+
<metadata>
575+
<desc>desc1</desc>
576+
<name>name1</name>
577+
</metadata>
578+
<extensions xmlns='http://www.example.com'>
579+
<element1 />
580+
<element2 />
581+
</extensions>
582+
<wpt lat='0.1' lon='2.3' />
583+
<rte><rtept lat='4.5' lon='6.7' /></rte>
584+
<trk><trkseg><trkpt lat='8.9' lon='10.11' /></trkseg></trk>
585+
<metadata>
586+
<desc>desc2</desc>
587+
<keywords>kwds2</keywords>
588+
</metadata>
589+
<extensions xmlns='http://www.example.com'>
590+
<element2 />
591+
<element3 />
592+
</extensions>
593+
</gpx>
594+
";
595+
Assert.ThrowsAny<XmlException>(() => GpxFile.Parse(GpxText, null));
596+
597+
var gpx = GpxFile.Parse(GpxText, new GpxReaderSettings { IgnoreUnexpectedChildrenOfTopLevelElement = true });
598+
Assert.Equal("airbreather", gpx.Metadata.Creator);
599+
Assert.Equal("desc1", gpx.Metadata.Description);
600+
Assert.Equal("name1", gpx.Metadata.Name);
601+
Assert.Null(gpx.Metadata.Keywords);
602+
var extensions = Assert.IsAssignableFrom<IEnumerable<XElement>>(gpx.Extensions).ToArray();
603+
Assert.Contains(extensions, e => e.Name == XName.Get("element1", "http://www.example.com"));
604+
Assert.Contains(extensions, e => e.Name == XName.Get("element2", "http://www.example.com"));
605+
Assert.DoesNotContain(extensions, e => e.Name == XName.Get("element3", "http://www.example.com"));
606+
}
505607
}
506608
}

0 commit comments

Comments
 (0)