question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

React-native-uri no longer well supported - Can react-native-svg support SvgXmlData instead?

See original GitHub issue

Feature Request

React-native-uri no longer seems supported (last commit was in 2018 and many open PRs) - read more

Can react-native-community pull in this use case or otherwise take over and support this library?

Why it is needed

React-native-svg-uri is unique because it allows rendering SVGs from a data string. This is crucial for anyone composing their svgs on the server. (svgXmlData)

react-native-svg-uri currently also supports rendering a static image. IMO, react-native-uri doesn’t actually need to support fetching from a static file since react-native-svg-transformer fills that niche.

Solutions

One idea is to clone react-native-svg-uri into react-native-community/react-native-svg-uri, merge in the current existing pull requests and do minor bug fixes support the last few RN versions.

Another idea is to create a new SVG data rendering component from scratch inside react-native-svg import {SvgXmlData} from 'react-native-svg'; ... <SvgXmlData source={'svg'} />

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Reactions:4
  • Comments:16

github_iconTop GitHub Comments

3reactions
msandcommented, Aug 25, 2019

With uri support:

import React, { Component, useState, useEffect, useMemo } from "react";
import {
  Circle,
  ClipPath,
  Defs,
  Ellipse,
  G,
  Image,
  Line,
  LinearGradient,
  Mask,
  Path,
  Pattern,
  Polygon,
  Polyline,
  RadialGradient,
  Rect,
  Stop,
  Svg,
  Symbol,
  Text,
  TextPath,
  TSpan,
  Use
} from "react-native-svg";

export const tags = {
  svg: Svg,
  circle: Circle,
  ellipse: Ellipse,
  g: G,
  text: Text,
  tspan: TSpan,
  textPath: TextPath,
  path: Path,
  polygon: Polygon,
  polyline: Polyline,
  line: Line,
  rect: Rect,
  use: Use,
  image: Image,
  symbol: Symbol,
  defs: Defs,
  linearGradient: LinearGradient,
  radialGradient: RadialGradient,
  stop: Stop,
  clipPath: ClipPath,
  pattern: Pattern,
  mask: Mask
};

export function astToReact(child, i) {
  if (typeof child === "object") {
    const { Tag, props, children } = child;
    return (
      <Tag key={i} {...props}>
        {children.map(astToReact)}
      </Tag>
    );
  }
  return child;
}

export function SvgAst({ ast, override }) {
  const { props, children } = ast;
  return (
    <Svg {...props} {...override}>
      {children}
    </Svg>
  );
}

export function SvgXml({ xml, ...props }) {
  const ast = useMemo(() => xml && parse(xml), [xml]);
  return (ast && <SvgAst ast={ast} override={props} />) || null;
}

async function fetchText(uri) {
  const response = await fetch(uri);
  return await response.text();
}

const err = console.error.bind(console);

export function SvgUri({ uri, ...props }) {
  const [xml, setXml] = useState();
  useEffect(() => {
    fetchText(uri)
      .then(setXml)
      .catch(err);
  }, [uri]);
  return (xml && <SvgXml xml={xml} {...props} />) || null;
}

export class SvgFromXml extends Component {
  state = {};
  componentDidMount() {
    const { xml } = this.props;
    this.parse(xml);
  }
  componentDidUpdate(prevProps) {
    const { xml } = this.props;
    if (xml !== prevProps.xml) {
      this.parse(xml);
    }
  }
  parse(xml) {
    try {
      const ast = parse(xml);
      this.setState({ ast });
    } catch (e) {
      console.error(e);
    }
  }
  render() {
    const { ast } = this.state;
    return ast ? <SvgAst ast={ast} override={this.props} /> : null;
  }
}

export class SvgFromUri extends Component {
  state = {};
  componentDidMount() {
    const { uri } = this.props;
    this.fetch(uri);
  }
  componentDidUpdate(prevProps) {
    const { uri } = this.props;
    if (uri !== prevProps.uri) {
      this.fetch(uri);
    }
  }
  async fetch(uri) {
    try {
      const xml = await fetchText(uri);
      this.setState({ xml });
    } catch (e) {
      console.error(e);
    }
  }
  render() {
    const { xml } = this.state;
    return xml ? <SvgFromXml xml={xml} {...this.props} /> : null;
  }
}

const upperCase = (match, letter) => letter.toUpperCase();

const camelCase = phrase => phrase.replace(/-([a-z])/g, upperCase);

export function getStyle(string) {
  const style = {};
  const declarations = string.split(";");
  for (let i = 0, l = declarations.length; i < l; i++) {
    const declaration = declarations[i].split(":");
    const property = declaration[0];
    const value = declaration[1];
    style[camelCase(property.trim())] = value.trim();
  }
  return style;
}

// slimmed down parser based on https://github.com/Rich-Harris/svg-parser

function locate(source, search) {
  const lines = source.split("\n");
  for (let line = 0, l = lines.length; line < l; line++) {
    const { length } = lines[line];
    if (search < length) {
      return { line, column: search };
    } else {
      search -= length;
    }
  }
}

const validNameCharacters = /[a-zA-Z0-9:_-]/;
const whitespace = /[\s\t\r\n]/;
const quotemark = /['"]/;

function repeat(str, i) {
  let result = "";
  while (i--) result += str;
  return result;
}

export function parse(source) {
  const length = source.length;
  let currentElement = null;
  let state = metadata;
  let children = null;
  let root = null;
  let stack = [];

  function error(message) {
    const { line, column } = locate(source, i);
    const before = source
      .slice(0, i)
      .replace(/^\t+/, match => repeat("  ", match.length));
    const beforeLine = /(^|\n).*$/.exec(before)[0];
    const after = source.slice(i);
    const afterLine = /.*(\n|$)/.exec(after)[0];

    const snippet = `${beforeLine}${afterLine}\n${repeat(
      " ",
      beforeLine.length
    )}^`;

    throw new Error(
      `${message} (${line}:${column}). If this is valid SVG, it's probably a bug. Please raise an issue\n\n${snippet}`
    );
  }

  function metadata() {
    while (
      (i < length && source[i] !== "<") ||
      !validNameCharacters.test(source[i + 1])
    ) {
      i++;
    }

    return neutral();
  }

  function neutral() {
    let text = "";
    while (i < length && source[i] !== "<") text += source[i++];

    if (/\S/.test(text)) {
      children.push(text);
    }

    if (source[i] === "<") {
      return openingTag;
    }

    return neutral;
  }

  function openingTag() {
    const char = source[i];

    if (char === "?") return neutral; // <?xml...

    if (char === "!") {
      if (source.slice(i + 1, i + 3) === "--") return comment;
      if (source.slice(i + 1, i + 8) === "[CDATA[") return cdata;
      if (/doctype/i.test(source.slice(i + 1, i + 8))) return neutral;
    }

    if (char === "/") return closingTag;

    const tag = getName();
    const props = {};
    const element = {
      tag,
      props,
      children: [],
      Tag: tags[tag]
    };

    if (currentElement) {
      children.push(element);
    } else {
      root = element;
    }

    getAttributes(props);

    const { style } = props;
    if (style) {
      props.style = getStyle(style);
    }

    let selfClosing = false;

    if (source[i] === "/") {
      i += 1;
      selfClosing = true;
    }

    if (source[i] !== ">") {
      error("Expected >");
    }

    if (!selfClosing) {
      currentElement = element;
      ({ children } = element);
      stack.push(element);
    }

    return neutral;
  }

  function comment() {
    const index = source.indexOf("-->", i);
    if (!~index) error("expected -->");

    i = index + 2;
    return neutral;
  }

  function cdata() {
    const index = source.indexOf("]]>", i);
    if (!~index) error("expected ]]>");

    i = index + 2;
    return neutral;
  }

  function closingTag() {
    const tag = getName();

    if (!tag) error("Expected tag name");

    if (tag !== currentElement.tag) {
      error(
        `Expected closing tag </${tag}> to match opening tag <${
          currentElement.tag
        }>`
      );
    }

    if (source[i] !== ">") {
      error("Expected >");
    }

    stack.pop();
    currentElement = stack[stack.length - 1];
    if (currentElement) {
      ({ children } = currentElement);
    }

    return neutral;
  }

  function getName() {
    let name = "";
    while (i < length && validNameCharacters.test(source[i]))
      name += source[i++];

    return name;
  }

  function getAttributes(props) {
    while (i < length) {
      if (!whitespace.test(source[i])) return;
      allowSpaces();

      const name = getName();
      if (!name) return;

      let value = true;

      allowSpaces();
      if (source[i] === "=") {
        i += 1;
        allowSpaces();

        value = getAttributeValue();
        if (!isNaN(value) && value.trim() !== "") value = +value; // TODO whitelist numeric attributes?
      }

      props[camelCase(name)] = value;
    }
  }

  function getAttributeValue() {
    return quotemark.test(source[i])
      ? getQuotedAttributeValue()
      : getUnquotedAttributeValue();
  }

  function getUnquotedAttributeValue() {
    let value = "";
    do {
      const char = source[i];
      if (char === " " || char === ">" || char === "/") {
        return value;
      }

      value += char;
      i += 1;
    } while (i < length);

    return value;
  }

  function getQuotedAttributeValue() {
    const quotemark = source[i++];

    let value = "";
    let escaped = false;

    while (i < length) {
      const char = source[i++];
      if (char === quotemark && !escaped) {
        return value;
      }

      if (char === "\\" && !escaped) {
        escaped = true;
      }

      value += escaped ? `\\${char}` : char;
      escaped = false;
    }
  }

  function allowSpaces() {
    while (i < length && whitespace.test(source[i])) i += 1;
  }

  let i = 0;
  while (i < length) {
    if (!state) error("Unexpected character");
    state = state();
    i += 1;
  }

  if (state !== neutral) {
    error("Unexpected end of input");
  }

  root.children = root.children.map(astToReact);

  return root;
}

2reactions
etaikleincommented, Aug 26, 2019

Amazing work and quick response - thank you so much!

I was able to run the example in the readme no problem. However, I found a few cases blocking me from using my existing web svgs:

Failure 1: Adding inline style to text

<svg width="32" height="32" viewBox="0 0 32 32">
  <text x="1" y="20" style="fill:red;">TEST</text>
</svg>

TypeError: Cannot read property 'trim' of undefined

Failure 2: Including a <style> tag

<svg width="32" height="32" viewBox="0 0 32 32">
  <style>
    .small { font: italic 13px sans-serif; }
  </style>
  <text x="1" y="20" class="small">TEST</text>
</svg>
ExceptionsManager.js:82 Unhandled JS Exception: Invariant Violation: Invariant Violation: Invariant Violation: Invariant Violation: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.

Check the render method of `SvgXml`.

This error is located at:
    in RNSVGGroup (at G.js:23)
    in G (at Svg.js:127)
    in RNSVGSvgView (at Svg.js:116)
    in Svg (at xml.js:53)
    in SvgAst (at xml.js:61)

Failure 3: Accessibility elements title + desc

<svg version="1" id="cat" viewBox="0 0 720 800" aria-labelledby="catTitle catDesc" role="img">
  <title id="catTitle">Pixels, My Super-friendly Cat</title>
  <desc id="catDesc">An illustrated gray cat with bright green blinking eyes.</desc>
  <text x="20" y="35">TODO: cat</text>
</svg>
Unhandled JS Exception: Invariant Violation: Invariant Violation: Invariant Violation: Invariant Violation: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.

Check the render method of `SvgXml`.

This error is located at:
    in RNSVGGroup (at G.js:23)
    in G (at Svg.js:127)
    in RNSVGSvgView (at Svg.js:116)
    in Svg (at xml.js:53)
    in SvgAst (at xml.js:61)

It also broke on <use xlink:href ... /> but that’s not supported in svg2, and href works fine.

Thanks again!

Read more comments on GitHub >

github_iconTop Results From Across the Web

Can react-native-svg support SvgXmlData instead? #1074
etaiklein changed the title React-native-uri no longer well supported - Can this library support the SvgXmlData feature instead?
Read more >
How to show SVG file on React Native? - Stack Overflow
This approach use a vector font (from SVG) instead a SVG file. PS: react-native-vector-icons is ... SVG does not support directly in Native...
Read more >
react-native-svg - npm
react-native-svg provides SVG support to React Native on iOS, Android, macOS, Windows, and a compatibility layer for the web.
Read more >
How to use SVGs in React Native with Expo - Level Up Coding
SVG is a vector format that can scale to any size without losing its quality, ... to react-native-svg and using the SvgUri component...
Read more >
weixin_39980903的博客_CSDN博客-领域博主
... 实现读取服务器文件,ssm框架实现发送邮件,mcrpg职业系统服务器,[RPG|机制][V]MCMMO —— 老牌的RPG职业与技能插件丨SpigotMC销量#No.1 [1.6-1.13].
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found