Revision history for Perl extension URI::Template
+0.17 2013-03-11
+ - Special case return from empty hash and array values in query strings
+
0.16 2012-05-30
- Conform to latest spec RFC 6570 (RT 66651)
- Re-use test suite from official github repo
Makefile.PL
MANIFEST This list of files
META.yml
-MYMETA.json
-MYMETA.yml
README
t/01-use.t
t/10-basic.t
t/98-pod.t
t/99-podcoverage.t
t/cases/extended-tests.json
+t/cases/json2xml.xslt
t/cases/negative-tests.json
t/cases/spec-examples-by-section.json
t/cases/spec-examples.json
+t/cases/transform-json-tests.xslt
resources:
license: http://dev.perl.org/licenses/
repository: http://github.com/bricas/uri-template
-version: 0.16
+version: 0.17
+++ /dev/null
-{
- "abstract" : "Object for handling URI templates (RFC 6570)",
- "author" : [
- "=over 4"
- ],
- "dynamic_config" : 0,
- "generated_by" : "Module::Install version 1.06, CPAN::Meta::Converter version 2.120921",
- "license" : [
- "perl_5"
- ],
- "meta-spec" : {
- "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec",
- "version" : "2"
- },
- "name" : "URI-Template",
- "no_index" : {
- "directory" : [
- "inc",
- "t"
- ]
- },
- "prereqs" : {
- "build" : {
- "requires" : {
- "ExtUtils::MakeMaker" : "6.59",
- "Test::More" : "0"
- }
- },
- "configure" : {
- "requires" : {
- "ExtUtils::MakeMaker" : "6.59"
- }
- },
- "runtime" : {
- "requires" : {
- "URI" : "0",
- "URI::Escape" : "0",
- "Unicode::Normalize" : "0",
- "perl" : "5.006"
- }
- }
- },
- "release_status" : "stable",
- "resources" : {
- "license" : [
- "http://dev.perl.org/licenses/"
- ],
- "repository" : {
- "url" : "http://github.com/bricas/uri-template"
- }
- },
- "version" : "0.16"
-}
+++ /dev/null
----
-abstract: 'Object for handling URI templates (RFC 6570)'
-author:
- - '=over 4'
-build_requires:
- ExtUtils::MakeMaker: 6.59
- Test::More: 0
-configure_requires:
- ExtUtils::MakeMaker: 6.59
-dynamic_config: 0
-generated_by: 'Module::Install version 1.06, CPAN::Meta::Converter version 2.120921'
-license: perl
-meta-spec:
- url: http://module-build.sourceforge.net/META-spec-v1.4.html
- version: 1.4
-name: URI-Template
-no_index:
- directory:
- - inc
- - t
-requires:
- URI: 0
- URI::Escape: 0
- Unicode::Normalize: 0
- perl: 5.006
-resources:
- license: http://dev.perl.org/licenses/
- repository: http://github.com/bricas/uri-template
-version: 0.16
* Ricardo SIGNES <rjbs@cpan.org>
COPYRIGHT AND LICENSE
- Copyright 2007-2012 by Brian Cassidy
+ Copyright 2007-2013 by Brian Cassidy
This library is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.
use strict;
use warnings;
-our $VERSION = '0.16';
+our $VERSION = '0.17';
use URI;
use URI::Escape ();
$join = '&' if $exp->{ op } =~ /[?&]/;
if ( ref $value eq 'ARRAY' ) {
- return unless @$value;
+ if( !@$value ) {
+ return if $var->{ explode };
+ return $var->{ name } . '=';
+ }
if ( $var->{ explode } ) {
return join( $join,
map { $var->{ name } . '=' . _quote( $_, $safe ) } @$value );
}
}
elsif ( ref $value eq 'HASH' ) {
- return unless keys %$value;
+ if( !keys %$value ) {
+ return if $var->{ explode };
+ return $var->{ name } . '=';
+ }
if ( $var->{ explode } ) {
return join(
$join,
=head1 COPYRIGHT AND LICENSE
-Copyright 2007-2012 by Brian Cassidy
+Copyright 2007-2013 by Brian Cassidy
This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.
BEGIN {
eval "use JSON ();";
plan skip_all => "JSON required" if $@;
+
+ eval { JSON->VERSION( 2 ) };
+ plan skip_all => "JSON version 2 of greater required" if $@;
+
plan( 'no_plan' );
use_ok( 'URI::Template' );
}
my $data = do { local $/; <$json> };
close( $json );
- eval { JSON->VERSION( 2 ) };
- my $suite = $@ ? JSON::jsonToObj( $data ) : JSON::from_json( $data );
+ my $suite = JSON->new->utf8( 1 )->decode( $data );
for my $name ( sort keys %$suite ) {
my $info = $suite->{ $name };
"geocode" : ["37.76","-122.427"],
"first_name" : "John",
"last.name" : "Doe",
- "Some%20Thing" : "foo"
+ "Some%20Thing" : "foo",
+ "number" : 6,
+ "long" : 37.76,
+ "lat" : -122.427,
+ "group_id" : "12345",
+ "query" : "PREFIX dc: <http://purl.org/dc/elements/1.1/> SELECT ?book ?who WHERE { ?book dc:creator ?who }",
+ "uri" : "http://example.org/?uri=http%3A%2F%2Fexample.org%2F",
+ "word" : "drücken",
+ "Stra%C3%9Fe" : "Grüner Weg",
+ "random" : "šö䟜ñꀣ¥‡ÑÒÓÔÕÖרÙÚàáâãäåæçÿ",
+ "assoc_special_chars" :
+ { "šö䟜ñꀣ¥‡ÑÒÓÔÕ" : "ÖרÙÚàáâãäåæçÿ" }
},
"testcases":[
[ "/search.json?q=URI%20Templates&geocode=37.76,-122.427&lang=en&page=5",
"/search.json?q=URI%20Templates&geocode=-122.427,37.76&lang=en&page=5"]
],
- ["/test{/Some%20Thing}", "/test/foo" ]
+ ["/test{/Some%20Thing}", "/test/foo" ],
+ ["/set{?number}", "/set?number=6"],
+ ["/loc{?long,lat}" , "/loc?long=37.76&lat=-122.427"],
+ ["/base{/group_id,first_name}/pages{/page,lang}{?format,q}","/base/12345/John/pages/5/en?format=json&q=URI%20Templates"],
+ ["/sparql{?query}", "/sparql?query=PREFIX%20dc%3A%20%3Chttp%3A%2F%2Fpurl.org%2Fdc%2Felements%2F1.1%2F%3E%20SELECT%20%3Fbook%20%3Fwho%20WHERE%20%7B%20%3Fbook%20dc%3Acreator%20%3Fwho%20%7D"],
+ ["/go{?uri}", "/go?uri=http%3A%2F%2Fexample.org%2F%3Furi%3Dhttp%253A%252F%252Fexample.org%252F"],
+ ["/service{?word}", "/service?word=dr%C3%BCcken"],
+ ["/lookup{?Stra%C3%9Fe}", "/lookup?Stra%C3%9Fe=Gr%C3%BCner%20Weg"],
+ ["{random}" , "%C5%A1%C3%B6%C3%A4%C5%B8%C5%93%C3%B1%C3%AA%E2%82%AC%C2%A3%C2%A5%E2%80%A1%C3%91%C3%92%C3%93%C3%94%C3%95%C3%96%C3%97%C3%98%C3%99%C3%9A%C3%A0%C3%A1%C3%A2%C3%A3%C3%A4%C3%A5%C3%A6%C3%A7%C3%BF"],
+ ["{?assoc_special_chars*}", "?%C5%A1%C3%B6%C3%A4%C5%B8%C5%93%C3%B1%C3%AA%E2%82%AC%C2%A3%C2%A5%E2%80%A1%C3%91%C3%92%C3%93%C3%94%C3%95=%C3%96%C3%97%C3%98%C3%99%C3%9A%C3%A0%C3%A1%C3%A2%C3%A3%C3%A4%C3%A5%C3%A6%C3%A7%C3%BF"]
]
},
"Additional Examples 2":{
"/albums/person?fields=name,id,picture&token=12345"]
]
]
+ },
+ "Additional Examples 3: Empty Variables":{
+ "variables" : {
+ "empty_list" : [],
+ "empty_assoc" : {}
+ },
+ "testcases":[
+ [ "{/empty_list}", [ "" ] ],
+ [ "{/empty_list*}", [ "" ] ],
+ [ "{?empty_list}", [ "?empty_list="] ],
+ [ "{?empty_list*}", [ "" ] ],
+ [ "{?empty_assoc}", [ "?empty_assoc=" ] ],
+ [ "{?empty_assoc*}", [ "" ] ]
+ ]
+ },
+ "Additional Examples 4: Numeric Keys":{
+ "variables" : {
+ "42" : "The Answer to the Ultimate Question of Life, the Universe, and Everything",
+ "1337" : ["leet", "as","it", "can","be"],
+ "german" : {
+ "11": "elf",
+ "12": "zwölf"
+ }
+ },
+ "testcases":[
+ [ "{42}", "The%20Answer%20to%20the%20Ultimate%20Question%20of%20Life%2C%20the%20Universe%2C%20and%20Everything"],
+ [ "{?42}", "?42=The%20Answer%20to%20the%20Ultimate%20Question%20of%20Life%2C%20the%20Universe%2C%20and%20Everything"],
+ [ "{1337}", "leet,as,it,can,be"],
+ [ "{?1337*}", "?1337=leet&1337=as&1337=it&1337=can&1337=be"],
+ [ "{?german*}", [ "?11=elf&12=zw%C3%B6lf", "?12=zw%C3%B6lf&11=elf"] ]
+ ]
}
}
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Downloaded on 12/6/2012 from http://www.gerixsoft.com/blog/xslt/json2xml -->
+
+<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+
+ <xsl:template match="json">
+ <xsl:copy>
+ <xsl:copy-of select="@*"/>
+ <xsl:call-template name="json2xml">
+ <xsl:with-param name="text" select="."/>
+ </xsl:call-template>
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template name="json2xml">
+ <xsl:param name="text"/>
+ <xsl:variable name="mode0">
+ <xsl:variable name="regexps" select="'//(.*?)\n', '/\*(.*?)\*/', '(''|")(([^\\]|\\[\\"''/btnvfr])*?)\3', '(-?\d+(\.\d+([eE][+-]?\d+)?|[eE][+-]?\d+))', '(-?[1-9]\d*)', '(-?0[0-7]+)', '(-?0x[0-9a-fA-F]+)', '([:,\{\}\[\]])', '(true|false)', '(null)'"/>
+ <xsl:analyze-string select="$text" regex="{string-join($regexps,'|')}" flags="s">
+ <xsl:matching-substring>
+ <xsl:choose>
+ <!-- single line comment -->
+ <xsl:when test="regex-group(1)">
+ <xsl:comment>
+ <xsl:value-of select="regex-group(1)"/>
+ </xsl:comment>
+ <xsl:text> </xsl:text>
+ </xsl:when>
+ <!-- multi line comment -->
+ <xsl:when test="regex-group(2)">
+ <xsl:comment>
+ <xsl:value-of select="regex-group(2)"/>
+ </xsl:comment>
+ </xsl:when>
+ <!-- string -->
+ <xsl:when test="regex-group(3)">
+ <string>
+ <xsl:analyze-string select="regex-group(4)" regex="\\([\\"'/btnvfr])" flags="s">
+ <xsl:matching-substring>
+ <xsl:variable name="s" select="regex-group(1)"/>
+ <xsl:choose>
+ <xsl:when test="$s=('\', '"', '''', '/')">
+ <xsl:value-of select="regex-group(1)"/>
+ </xsl:when>
+ <xsl:when test="$s='b'">
+ <!--xsl:text></xsl:text-->
+ <xsl:message select="'escape sequense \b is not supported by XML'"/>
+ <xsl:text>\b</xsl:text>
+ </xsl:when>
+ <xsl:when test="$s='t'">
+ <xsl:text>	</xsl:text>
+ </xsl:when>
+ <xsl:when test="$s='n'">
+ <xsl:text> </xsl:text>
+ </xsl:when>
+ <xsl:when test="$s='v'">
+ <!--xsl:text></xsl:text-->
+ <xsl:message select="'escape sequence \v is not supported by XML'"/>
+ <xsl:text>\v</xsl:text>
+ </xsl:when>
+ <xsl:when test="$s='f'">
+ <!--xsl:text></xsl:text-->
+ <xsl:message select="'escape sequence \f is not supported by XML'"/>
+ <xsl:text>\f</xsl:text>
+ </xsl:when>
+ <xsl:when test="$s='r'">
+ <xsl:text> </xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:message terminate="yes" select="'internal error'"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:matching-substring>
+ <xsl:non-matching-substring>
+ <xsl:value-of select="."/>
+ </xsl:non-matching-substring>
+ </xsl:analyze-string>
+ </string>
+ </xsl:when>
+ <!-- double -->
+ <xsl:when test="regex-group(6)">
+ <double>
+ <xsl:value-of select="regex-group(6)"/>
+ </double>
+ </xsl:when>
+ <!-- integer -->
+ <xsl:when test="regex-group(9)">
+ <integer>
+ <xsl:value-of select="regex-group(9)"/>
+ </integer>
+ </xsl:when>
+ <!-- octal -->
+ <xsl:when test="regex-group(10)">
+ <integer>
+ <xsl:value-of xmlns:Integer="java:java.lang.Integer" select="Integer:parseInt(regex-group(10), 8)"/>
+ </integer>
+ </xsl:when>
+ <!-- hex -->
+ <xsl:when test="regex-group(11)">
+ <integer>
+ <xsl:value-of xmlns:Integer="java:java.lang.Integer" select="Integer:parseInt(replace(regex-group(11), '0x', ''), 16)"/>
+ </integer>
+ </xsl:when>
+ <!-- symbol -->
+ <xsl:when test="regex-group(12)">
+ <symbol>
+ <xsl:value-of select="regex-group(12)"/>
+ </symbol>
+ </xsl:when>
+ <!-- boolean -->
+ <xsl:when test="regex-group(13)">
+ <boolean>
+ <xsl:value-of select="regex-group(13)"/>
+ </boolean>
+ </xsl:when>
+ <!-- null -->
+ <xsl:when test="regex-group(14)">
+ <null />
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:message terminate="yes" select="'internal error'"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:matching-substring>
+ <xsl:non-matching-substring>
+ <xsl:if test="normalize-space()!=''">
+ <xsl:message select="concat('unknown token: ', .)"/>
+ <xsl:value-of select="."/>
+ </xsl:if>
+ </xsl:non-matching-substring>
+ </xsl:analyze-string>
+ </xsl:variable>
+ <xsl:variable name="mode1">
+ <xsl:apply-templates mode="json2xml1" select="$mode0/node()[1]"/>
+ </xsl:variable>
+ <xsl:variable name="mode2">
+ <xsl:apply-templates mode="json2xml2" select="$mode1"/>
+ </xsl:variable>
+ <xsl:variable name="mode3">
+ <xsl:apply-templates mode="json2xml3" select="$mode2"/>
+ </xsl:variable>
+ <xsl:copy-of select="$mode3"/> <!-- change $mode3 to $mode[0-2] for easy debug -->
+ </xsl:template>
+
+ <!-- json2xml1 mode: group content between {} and [] into object and array elements -->
+
+ <xsl:template mode="json2xml1" match="node()" priority="-9">
+ <xsl:copy-of select="."/>
+ <xsl:apply-templates mode="json2xml1" select="following-sibling::node()[1]"/>
+ </xsl:template>
+
+ <xsl:template mode="json2xml1" match="symbol[.=('}',']')]"/>
+
+ <xsl:template mode="json2xml1" match="symbol[.=('{','[')]">
+ <xsl:element name="{if (.='{') then 'object' else 'array'}">
+ <xsl:apply-templates mode="json2xml1" select="following-sibling::node()[1]"/>
+ </xsl:element>
+ <xsl:variable name="level" select="count(preceding-sibling::symbol[.=('{','[')])-count(preceding-sibling::symbol[.=('}',']')])+1"/>
+ <xsl:variable name="ender"
+ select="following-sibling::symbol[.=('}',']') and count(preceding-sibling::symbol[.=('{','[')])-count(preceding-sibling::symbol[.=('}',']')])=$level][1]"/>
+ <xsl:apply-templates mode="json2xml1" select="$ender/following-sibling::node()[1]"/>
+ </xsl:template>
+
+ <!-- json2xml2 mode: group <string>:<string|integer|double|object|array> into field element -->
+
+ <xsl:template priority="-9" mode="json2xml2" match="@*|node()">
+ <xsl:copy>
+ <xsl:apply-templates mode="json2xml2" select="@*|node()"/>
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template mode="json2xml2"
+ match="string[following-sibling::*[1]/self::symbol[.=':'] and following-sibling::*[2]/(self::string|self::integer|self::double|self::boolean|self::object|self::array|self::null)]"/>
+
+ <xsl:template mode="json2xml2"
+ match="symbol[.=':'][preceding-sibling::*[1]/self::string and following-sibling::*[1]/(self::string|self::integer|self::double|self::boolean|self::object|self::array|self::null)]">
+ <field name="{preceding-sibling::*[1]}">
+ <xsl:for-each select="following-sibling::*[1]">
+ <xsl:copy>
+ <xsl:apply-templates mode="json2xml2" select="@*|node()"/>
+ </xsl:copy>
+ </xsl:for-each>
+ </field>
+ </xsl:template>
+
+ <xsl:template mode="json2xml2"
+ match="*[self::string|self::integer|self::double|self::boolean|self::object|self::array|self::null][preceding-sibling::*[2]/self::string and preceding-sibling::*[1]/self::symbol[.=':']]"/>
+
+ <!-- json2xml3 mode: drop comma between consecutive field and object elements -->
+
+ <xsl:template priority="-9" mode="json2xml3" match="@*|node()">
+ <xsl:copy>
+ <xsl:apply-templates mode="json2xml3" select="@*|node()"/>
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template mode="json2xml3" match="object/symbol[.=','][preceding-sibling::*[1]/self::field and following-sibling::*[1]/self::field]"/>
+
+ <xsl:template mode="json2xml3" match="array/symbol[.=','][preceding-sibling::*[1]/(self::string|self::integer|self::double|self::boolean|self::object|self::array|self::null) and following-sibling::*[1]/(self::string|self::integer|self::double|self::boolean|self::object|self::array|self::null)]"/>
+
+</xsl:stylesheet>
\ No newline at end of file
"Failure Tests":{
"level":4,
"variables":{
- "id" : "thing",
- "var" : "value",
- "hello" : "Hello World!",
- "empty" : "",
- "path" : "/foo/bar",
- "x" : "1024",
- "y" : "768",
- "list" : ["red", "green", "blue"],
- "keys" : { "semi" : ";", "dot" : ".", "comma" : ","},
- "example" : "red",
- "searchTerms" : "uri templates"
+ "id" : "thing",
+ "var" : "value",
+ "hello" : "Hello World!",
+ "empty" : "",
+ "path" : "/foo/bar",
+ "x" : "1024",
+ "y" : "768",
+ "list" : ["red", "green", "blue"],
+ "keys" : { "semi" : ";", "dot" : ".", "comma" : ","},
+ "example" : "red",
+ "searchTerms" : "uri templates",
+ "~thing" : "some-user",
+ "default-graph-uri" : ["http://www.example/book/","http://www.example/papers/"],
+ "query" : "PREFIX dc: <http://purl.org/dc/elements/1.1/> SELECT ?book ?who WHERE { ?book dc:creator ?who }"
+
},
"testcases":[
[ "{/id*", false ],
[ "x{?empty|foo=none}" , false ],
[ "/h{#hello+}" , false ],
[ "/h#{hello+}" , false ],
- [ "/vars/:var" , false ],
[ "{keys:1}", false ],
[ "{+keys:1}", false ],
[ "{;keys:1*}", false ],
- [ "?{-join|&|var,list}" , false ]
+ [ "?{-join|&|var,list}" , false ],
+ [ "/people/{~thing}", false],
+ [ "/sparql{?query){&default-graph-uri*}", false ],
+ [ "/resolution{?x, y}" , false ]
+
]
}
}
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+
+<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+
+ <xsl:include href="json2xml.xslt"/>
+
+ <!-- the input test files need to be supplied as a sequence of file names only (no extension) -->
+ <xsl:param name="tests" select="('extended-tests', 'negative-tests', 'spec-examples-by-section', 'spec-examples')"/>
+ <xsl:param name="json-ext" select="'json'"/>
+ <xsl:param name="xml-ext" select="'xml'"/>
+
+ <!-- run this stylesheet with any input to generate the XML test files at output. -->
+ <!-- (a popular way of doing this is to supply the XSLT to itself as the input file.) -->
+
+ <xsl:template match="/">
+ <xsl:for-each select="$tests">
+ <xsl:variable name="file" select="current()"/>
+ <xsl:variable name="json">
+ <json>
+ <xsl:value-of select="unparsed-text(concat($file, '.', $json-ext))"/>
+ </json>
+ </xsl:variable>
+ <xsl:variable name="xml">
+ <xsl:apply-templates select="$json/json"/>
+ </xsl:variable>
+ <xsl:result-document href="{$file}.{$xml-ext}" method="xml" indent="yes">
+ <tests>
+ <xsl:for-each select="$xml/json/object/field">
+ <test name="{@name}">
+ <xsl:if test="exists(object/field[1][@name eq 'level'])">
+ <xsl:attribute name="level" select="object/field[1][@name eq 'level']/integer/text()"/>
+ </xsl:if>
+ <variables>
+ <xsl:for-each select="object/field[@name eq 'variables']/object/field">
+ <variable name="{@name}">
+ <xsl:copy-of select="*"/>
+ </variable>
+ </xsl:for-each>
+ </variables>
+ <testcases>
+ <xsl:for-each select="object/field[@name eq 'testcases']/array/array">
+ <testcase template="{*[1]}" result="{*[2]}"/>
+ </xsl:for-each> </testcases>
+ </test>
+ </xsl:for-each>
+ </tests>
+ </xsl:result-document>
+ </xsl:for-each>
+ </xsl:template>
+
+</xsl:stylesheet>
\ No newline at end of file