   includes: (substring) ->
     @key.indexOf(substring) >= 0
     ## ECMA6: @key.includes substring
+
+escapeId = (key) ->
+  ###
+  id/href follows the IRI spec [https://tools.ietf.org/html/rfc3987]:
+    ifragment      = *( ipchar / "/" / "?" )
+    ipchar         = iunreserved / pct-encoded / sub-delims / ":" / "@"
+    iunreserved    = ALPHA / DIGIT / "-" / "." / "_" / "~" / ucschar
+    pct-encoded    = "%" HEXDIG HEXDIG
+    sub-delims     = "!" / "$" / "&" / "'" / "(" / ")"
+                   / "*" / "+" / "," / ";" / "="
+    ucschar        = %xA0-D7FF / %xF900-FDCF / #%xFDF0-FFEF
+                   / %x10000-1FFFD / %x20000-2FFFD / %x30000-3FFFD
+                   / %x40000-4FFFD / %x50000-5FFFD / %x60000-6FFFD
+                   / %x70000-7FFFD / %x80000-8FFFD / %x90000-9FFFD
+                   / %xA0000-AFFFD / %xB0000-BFFFD / %xC0000-CFFFD
+                   / %xD0000-DFFFD / %xE1000-EFFFD
+  We also want to escape colon (:) which seems to cause trouble.
+  We use encodeURIComponent which escapes everything except
+    A-Z a-z 0-9 - _ . ! ~ * ' ( )
+  [https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent]
+  Unfortunately, Inkscape seems to ignore any %-encoded symbols; see
+  https://bugs.launchpad.net/inkscape/+bug/1737778
+  So we replace '%' with '$', an allowed character that's already escaped.
+  In the special case of a blank key, we use the special $BLANK$ which cannot
+  be generated by the escaping process.
+  ###
+  (encodeURIComponent key
+   .replace /%/g, '$'
+  ) or '$BLANK$'
 
 zeroSizeReplacement = 1
 

    @xml.documentElement.removeAttribute 'height' if @autoHeight
    @zIndex = zIndex @xml.documentElement
  id: ->
    ###
    id/href follows the IRI spec [https://tools.ietf.org/html/rfc3987]:
      ifragment      = *( ipchar / "/" / "?" )
      ipchar         = iunreserved / pct-encoded / sub-delims / ":" / "@"
      iunreserved    = ALPHA / DIGIT / "-" / "." / "_" / "~" / ucschar
      pct-encoded    = "%" HEXDIG HEXDIG
      sub-delims     = "!" / "$" / "&" / "'" / "(" / ")"
                     / "*" / "+" / "," / ";" / "="
      ucschar        = %xA0-D7FF / %xF900-FDCF / #%xFDF0-FFEF
                     / %x10000-1FFFD / %x20000-2FFFD / %x30000-3FFFD
                     / %x40000-4FFFD / %x50000-5FFFD / %x60000-6FFFD
                     / %x70000-7FFFD / %x80000-8FFFD / %x90000-9FFFD
                     / %xA0000-AFFFD / %xB0000-BFFFD / %xC0000-CFFFD
                     / %xD0000-DFFFD / %xE1000-EFFFD
    We also want to escape colon (:) which seems to cause trouble.
    We use encodeURIComponent which escapes everything except
      A-Z a-z 0-9 - _ . ! ~ * ' ( )
    [https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent]
    Unfortunately, Inkscape seems to ignore any %-encoded symbols; see
    https://bugs.launchpad.net/inkscape/+bug/1737778
    So we replace '%' with '$', an allowed character that's already escaped.
    In the special case of a blank key, we use the special $BLANK$ which cannot
    be generated by the escaping process.
    ###
    (encodeURIComponent @key
     .replace /%/g, '$'
    ) or '$BLANK$'
  use: -> @  ## do nothing for static symbol
  usesContext: false


    ## return values like {filename: ...} and JSX virtual dom elements,
    ## in addition to raw SVG strings.
    string = JSON.stringify result
    unless string of @versions
      version = @nversions++
      @versions[string] =
        Symbol.parse "#{@key}$v#{version}", result, @dirname
      @versions[string].id = => "#{escapeId @key}$v#{version}"
    @versions[string]
  usesContext: true

## Symbol to fall back to when encountering an unrecognized symbol.
