parseSvg = (data, from) => {
  const sax = SAX.parser(config.strict, config);
  /** @type {import('./types.js').XastRoot} */
  const root = { type: 'root', children: [] };
  /** @type {import('./types.js').XastParent} */
  let current = root;
  /** @type {import('./types.js').XastParent[]} */
  const stack = [root];

  /**
   * @param {import('./types.js').XastChild} node
   */
  const pushToContent = (node) => {
    current.children.push(node);
  };

  sax.ondoctype = (doctype) => {
    /** @type {import('./types.js').XastDoctype} */
    const node = {
      type: 'doctype',
      // TODO parse doctype for name, public and system to match xast
      name: 'svg',
      data: {
        doctype,
      },
    };
    pushToContent(node);
    const subsetStart = doctype.indexOf('[');
    if (subsetStart >= 0) {
      entityDeclaration.lastIndex = subsetStart;
      let entityMatch = entityDeclaration.exec(data);
      while (entityMatch != null) {
        sax.ENTITIES[entityMatch[1]] = entityMatch[2] || entityMatch[3];
        entityMatch = entityDeclaration.exec(data);
      }
    }
  };

  sax.onprocessinginstruction = (data) => {
    /** @type {import('./types.js').XastInstruction} */
    const node = {
      type: 'instruction',
      name: data.name,
      value: data.body,
    };
    pushToContent(node);
  };

  sax.oncomment = (comment) => {
    /** @type {import('./types.js').XastComment} */
    const node = {
      type: 'comment',
      value: comment.trim(),
    };
    pushToContent(node);
  };

  sax.oncdata = (cdata) => {
    /** @type {import('./types.js').XastCdata} */
    const node = {
      type: 'cdata',
      value: cdata,
    };
    pushToContent(node);
  };

  sax.onopentag = (data) => {
    /** @type {import('./types.js').XastElement} */
    const element = {
      type: 'element',
      name: data.name,
      attributes: {},
      children: [],
    };
    for (const [name, attr] of Object.entries(data.attributes)) {
      element.attributes[name] = attr.value;
    }
    pushToContent(element);
    current = element;
    stack.push(element);
  };

  sax.ontext = (text) => {
    if (current.type === 'element') {
      // prevent trimming of meaningful whitespace inside textual tags
      if (textElems.has(current.name)) {
        /** @type {import('./types.js').XastText} */
        const node = {
          type: 'text',
          value: text,
        };
        pushToContent(node);
      } else {
        const value = text.trim();

        if (value !== '') {
          /** @type {import('./types.js').XastText} */
          const node = {
            type: 'text',
            value,
          };
          pushToContent(node);
        }
      }
    }
  };

  sax.onclosetag = () => {
    stack.pop();
    current = stack[stack.length - 1];
  };

  sax.onerror = (e) => {
    const reason = e.message.split('\n')[0];
    const error = new SvgoParserError(
      reason,
      sax.line + 1,
      sax.column,
      data,
      from,
    );
    if (e.message.indexOf('Unexpected end') === -1) {
      throw error;
    }
  };

  sax.write(data).close();
  return root;
}
Convert SVG (XML) string to SVG-as-JS object.

@param {string} data
@param {string=} from
@returns {import('./types.js').XastRoot}