<p>由于Martijn发布了一个Python答案,并说Perl会变成行噪声,我觉得也需要Perl答案。</p>
<p>在Perl模块目录<a href="http://search.cpan.org/" rel="nofollow noreferrer">CPAN</a>上,有一个名为<a href="http://search.cpan.org/~andya/Geo-Gpx-0.21/lib/Geo/Gpx.pm" rel="nofollow noreferrer">Geo::Gpx</a>的模块。正如Martijn所说,GPX是一种XML格式。但幸运的是,有人已经将它制作成一个模块,为我们处理解析。我们要做的就是加载那个模块。</p>
<p>有几个模块可用于CSV处理,但是这个XML文件中的数据相当简单,因此我们不需要一个。我们可以通过内置的功能自己完成。</p>
<p>请考虑以下脚本。我马上解释。</p>
<pre><code>use strict;
use warnings;
use Geo::Gpx;
use DateTime;
# Open the GPX file
open my $fh_in, '<', 'fells_loop.gpx';
# Parse GPX
my $gpx = Geo::Gpx->new( input => $fh_in );
# Close the GPX file
close $fh_in;
# Open an output file
open my $fh_out, '>', 'fells_loop.csv';
# Print the header line to the file
print $fh_out "time,lat,lon,ele,name,sym,type,desc\n";
# The waypoints-method of the GEO::GPX-Object returns an array-ref
# which we can iterate in a foreach loop
foreach my $wp ( @{ $gpx->waypoints() } ) {
# Some fields seem to be optional so they are missing in the hash.
# We have to add an empty string by iterating over all the possible
# hash keys to put '' in them.
$wp->{$_} ||= '' for qw( time lat lon ele name sym type desc );
# The time is a unix timestamp, which is hard to read.
# We can make it an ISO8601 date with the DateTime module.
# We only do it if there already is a time, though.
if ($wp->{'time'}) {
$wp->{'time'} = DateTime->from_epoch( epoch => $wp->{'time'} )
->iso8601();
}
# Join the fields with a comma and print them to the output file
print $fh_out join(',', (
$wp->{'time'},
$wp->{'lat'},
$wp->{'lon'},
$wp->{'ele'},
$wp->{'name'},
$wp->{'sym'},
$wp->{'type'},
$wp->{'desc'},
)), "\n"; # Add a newline at the end
}
# Close the output file
close $fh_out;
</code></pre>
<hr/>
<p>让我们分步骤进行:</p>
<ul>
<li><a href="http://perldoc.perl.org/strict.html" rel="nofollow noreferrer">^{<cd1>}</a>和<a href="http://perldoc.perl.org/warnings.html" rel="nofollow noreferrer">^{<cd2>}</a>强制执行声明变量之类的规则,并告诉您最难找到的常见错误。</li>
<li><code>use Geo::Gpx</code>和<code>use DateTime</code>是我们使用的模块。<code>Geo::Gpx</code>将为我们处理解析。我们需要<code>DateTime</code>将unix时间戳转换为可读的日期和时间。</li>
<li><a href="http://perldoc.perl.org/functions/open.html" rel="nofollow noreferrer">^{<cd7>}</a>函数打开一个文件。<code>$fh_in</code>是保存文件句柄的变量。我们要读取的GPX文件是<strong>fellsúloop.GPX</strong>我冒昧地从<a href="http://www.topografix.com/gpx_sample_files.asp" rel="nofollow noreferrer">topografix.com</a>借用了这个文件。您可以在<a href="http://perldoc.perl.org/perlopentut.html" rel="nofollow noreferrer">perlopentut</a>中找到有关<code>open</code>的更多信息。</li>
<li>我们创建一个名为<code>$gpx</code>的新<code>Geo::Gpx</code>对象,并使用filehandle <code>$fh_in</code>告诉它从哪里读取XML数据。<code>new</code>方法由具有面向对象接口的所有Perl模块提供。</li>
<li><a href="http://perldoc.perl.org/functions/close.html" rel="nofollow noreferrer">^{<cd14>}</a>关闭文件句柄。</li>
<li>下一个<code>open</code>有一个<code>></code>来告诉Perl我们要写入这个文件句柄。</li>
<li>我们通过将filehandle作为<a href="http://perldoc.perl.org/functions/print.html" rel="nofollow noreferrer">print</a>的第一个参数来<code>print</code>文件句柄。注意,文件句柄后面没有逗号。<code>\n</code>是换行符。</li>
<li><p><a href="http://perldoc.perl.org/perlsyn.html#Compound-Statements" rel="nofollow noreferrer">^{<cd19>} loop</a>接受<code>Geo::Gpx</code>对象的<code>waypoints</code>-方法的返回值。此值是数组引用。把它看作一个保存数组的数组(如果您想了解更多关于引用的信息,请参见<a href="http://perldoc.perl.org/perlref.html" rel="nofollow noreferrer">perlref</a>)。在循环的每次迭代中,数组ref的下一个元素(表示GPX数据中的一个航路点)将被放入<code>$wp</code>。如果用<a href="http://search.cpan.org/~smueller/Data-Dumper-2.131/Dumper.pm" rel="nofollow noreferrer">^{<cd23>}</a>打印,则如下所示:</p>
<pre><code>$VAR1 = {
'ele' => '64.008000',
'lat' => '42.455956',
'time' => 991452424,
'name' => 'SOAPBOX',
'sym' => 'Cemetery',
'desc' => 'Soap Box Derby Track',
'lon' => '-71.107483',
'type' => 'Intersection'
};
</code></pre></li>
<li><p>现在<em>后缀</em><a href="http://perldoc.perl.org/functions/map.html" rel="nofollow noreferrer">^{<cd24>}</a>有点棘手。正如我们刚才看到的,hashref中有8个键。不幸的是,他们中的一些人有时失踪了。因为我们有<code>use warnings</code>,所以如果我们试图访问这些丢失的值之一,就会收到警告。我们必须创建这些键并在其中放置一个空字符串<code>''</code>。</p>
<p>在Perl中,<code>foreach</code>和<code>for</code>是完全可互换的,而且这两种语言也可以在单个表达式后面的<em>postfix</em>语法中使用。我们使用<code>qw</code>-操作符创建<code>for</code>将迭代的列表。<a href="http://perldoc.perl.org/5.14.2/perlop.html#Quote-Like-Operators" rel="nofollow noreferrer">^{<cd29>}</a>是<strong>带引号的单词的缩写,它就是这样做的:它返回字符串列表,但带引号。我们也可以说<code>('time', 'lat', 'long'... )</code>。</p>
<p>在表达式中,我们访问<code>$wp</code>的每个键。<code>$_</code>是循环变量。在第一次迭代中,它将保存“time”,然后保存“lat”等等。由于<code>$wp</code>是hashref,我们需要<code>-></code>来访问它的键。大括号表示它是hashref。只有当hash ref元素不是真值时,<a href="http://perldoc.perl.org/5.14.2/perlop.html#Assignment-Operators" rel="nofollow noreferrer">^{<cd37>} operator</a>才为它赋值。</p></li>
<li><p>现在,如果有一个时间值(如果没有设置日期,我们刚刚分配的空字符串将被视为“没有”),我们将用正确的日期替换unix时间戳。<a href="http://search.cpan.org/~drolsky/DateTime-0.75/lib/DateTime.pm" rel="nofollow noreferrer">DateTime</a>帮助我们做到这一点。<code>from_epoch</code>方法获取unix时间戳作为参数。它返回一个<code>DateTime</code>对象,我们可以直接使用它调用<code>iso8601</code>函数。</p>
<p>这叫做链锁。一些模块可以做到这一点。它类似于jQuery的JavaScript对象。hashref中的unix时间戳替换为<code>DateTime</code>操作的结果。</p></li>
<li>现在我们再次<code>print</code>到我们的文件句柄。<a href="http://perldoc.perl.org/5.14.2/functions/join.html" rel="nofollow noreferrer">^{<cd43>}</a>用于放置com在两个值之间。我们也在最后加了一个新行。</li>
<li>一旦我们完成了循环,我们就<code>close</code>文件句柄。</li>
<li>现在我们结束了!:)</li>
</ul>
<p>总而言之,我觉得这很简单,也很可读,不是吗?我试图使它成为一个健康的混合体,包含了过于冗长的语法和带有Perl风格的语法。</p>