urn.go 2.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. // Package urn implements RFC 2141.
  2. //
  3. // It provides a parser capable, given a string respecting valid syntax as per RFC 2141, to instantiate an URN type.
  4. // Furthermore this package provides methods to perform normalization and lexical equivalence on URN instances.
  5. package urn
  6. import (
  7. "strings"
  8. pcre "github.com/gijsbers/go-pcre"
  9. )
  10. var re = `^
  11. (?:(?P<pre>[uU][rR][nN]):(?!urn:))
  12. (?P<nid>[A-Za-z0-9][A-Za-z0-9-]{0,31}):
  13. (?P<nss>(?:[A-Za-z0-9()+,\-.:=@;$_!*']|[%][A-Fa-f0-9][A-Fa-f0-9])+)
  14. $`
  15. var pattern = pcre.MustCompile(re, pcre.EXTENDED)
  16. var hexrepr = pcre.MustCompile("[%][A-F0-9]{2}", 0)
  17. // URN represents an Uniform Resource Name.
  18. //
  19. // The general form represented is:
  20. //
  21. // urn:<id>:<ss>
  22. //
  23. // Details at https://tools.ietf.org/html/rfc2141.
  24. type URN struct {
  25. prefix string // Static prefix. Equal to "urn" when empty.
  26. ID string // Namespace identifier
  27. SS string // Namespace specific string
  28. }
  29. // Parse is responsible to create an URN instance from a string matching the correct URN syntax.
  30. func Parse(u string) (*URN, bool) {
  31. matcher := pattern.MatcherString(u, 0)
  32. matches := matcher.Matches()
  33. if matches {
  34. urn := &URN{}
  35. urn.prefix, _ = matcher.NamedString("pre")
  36. urn.ID, _ = matcher.NamedString("nid")
  37. urn.SS, _ = matcher.NamedString("nss")
  38. return urn, matches
  39. }
  40. return nil, matches
  41. }
  42. // String reassembles the URN into a valid URN string.
  43. //
  44. // This requires both ID and SS fields to be non-empty.
  45. // Otherwise it returns an empty string.
  46. //
  47. // Default URN prefix is "urn".
  48. func (u *URN) String() string {
  49. res := u.prefix
  50. if u.ID != "" && u.SS != "" {
  51. if res == "" {
  52. res += "urn"
  53. }
  54. res += ":" + u.ID + ":" + u.SS
  55. }
  56. return res
  57. }
  58. // Normalize turns the receiving URN into its norm version.
  59. //
  60. // Which means: lowercase prefix, lowercase namespace identifier, and immutate namespace specific string chars (except <hex> tokens which are lowercased).
  61. func (u *URN) Normalize() *URN {
  62. norm := ""
  63. ss := u.SS
  64. matcher := hexrepr.MatcherString(ss, 0)
  65. for matcher.MatchString(ss, 0) {
  66. indexes := matcher.Index()
  67. from := indexes[0]
  68. to := indexes[1]
  69. norm += ss[:from] + strings.ToLower(ss[from:to])
  70. ss = ss[to:]
  71. }
  72. norm += ss
  73. return &URN{
  74. prefix: "urn",
  75. ID: strings.ToLower(u.ID),
  76. SS: norm,
  77. }
  78. }
  79. // Equal checks the lexical equivalence of the current URN with another one.
  80. func (u *URN) Equal(x *URN) bool {
  81. return *u.Normalize() == *x.Normalize()
  82. }