说在前面

相信大家对 XML 都不会很陌生了,XML 被设计用来结构化、存储以及传输信息。最近在开发过程中发现,有一些旧接口返回的数据格式即是 XML 的格式,因此需要我们对返回的 XML 数据进行解析,转换成我们好处理的 JSON 数据结构,对此我花了一点时间写了一个简单的 XML 转换 JSON 格式的方法,所以也就有了今天的这一篇文章,虽然实现起来并不难,但还是希望这一篇文章可以对有需要的同学起到一丢丢的帮助。

XML 简介

什么是 XML

  • XML 指可扩展标记语言(EXtensible Markup Language)。
  • XML 是一种很像 HTML 的标记语言。
  • XML 的设计宗旨是传输数据,而不是显示数据。
  • XML 标签没有被预定义。您需要自行定义标签。
  • XML 被设计为具有自我描述性。
  • XML 是 W3C 的推荐标准。

XML 和 HTML 看起来是类似的,但其实他们是不同的,XML 和 HTML 为不同的目的而设计:

  • XML 被设计用来传输和存储数据,其焦点是数据的内容。
  • HTML 被设计用来显示数据,其焦点是数据的外观。

HTML 旨在显示信息,而 XML 旨在传输信息。

XML 用途

XML 应用于 Web 开发的许多方面,常用于简化数据的存储和共享。

  • XML 把数据从 HTML 分离

如果您需要在 HTML 文档中显示动态数据,那么每当数据改变时将花费大量的时间来编辑 HTML。

通过 XML,数据能够存储在独立的 XML 文件中。这样您就可以专注于使用 HTML/CSS 进行显示和布局,并确保修改底层数据不再需要对 HTML 进行任何的改变。

通过使用几行 JavaScript 代码,您就可以读取一个外部 XML 文件,并更新您的网页的数据内容。

  • XML 简化数据共享

在真实的世界中,计算机系统和数据使用不兼容的格式来存储数据。

XML 数据以纯文本格式进行存储,因此提供了一种独立于软件和硬件的数据存储方法。

这让创建不同应用程序可以共享的数据变得更加容易。

  • XML 简化数据传输

对开发人员来说,其中一项最费时的挑战一直是在互联网上的不兼容系统之间交换数据。

由于可以通过各种不兼容的应用程序来读取数据,以 XML 交换数据降低了这种复杂性。

XML 结构

  • XML 文档实例

XML 文档使用简单的具有自我描述性的语法:

<?xml version="1.0" encoding="UTF-8"?>
<note>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend!</body>
</note>

第一行是 XML 声明。它定义 XML 的版本(1.0)和所使用的编码(UTF-8 : 万国码, 可显示各种语言)。

下一行描述文档的根元素(像在说:“本文档是一个便签”):

<note>

接下来 4 行描述根的 4 个子元素(to, from, heading 以及 body):

<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend!</body>

最后一行定义根元素的结尾:

</note>

您可以假设,从这个实例中,XML 文档包含了一张 Jani 写给 Tove 的便签。

XML 具有出色的自我描述性,您同意吗?

  • XML 文档形成一种树结构

XML 文档必须包含根元素。该元素是所有其他元素的父元素。

XML 文档中的元素形成了一棵文档树。这棵树从根部开始,并扩展到树的最底端。

所有的元素都可以有子元素:

<root>
<child>
<subchild>.....</subchild>
</child>
</root>

父、子以及同胞等术语用于描述元素之间的关系。父元素拥有子元素。相同层级上的子元素成为同胞(兄弟或姐妹)。

所有的元素都可以有文本内容和属性(类似 HTML 中)。

实现思路

简单地对 XML 结构进行了一个复习之后,我们便可以开始准备编写我们的转换工具了:

原生手写

  • 1、找出 XML 中的标签

这里我们只需要简单的对 xml 字符串进行一次遍历,遍历到<时则说明遍历到标签了,我们只需要将其后面的字符串记录起来,直到遇到>时则说明当前标签结束,左标签和右标签我们可以通过/来做区分,以/开头的标签即为右标签。

  • 2、找出标签对中的值

在第一步中我们可以找到所有的标签,在遇到左标签时,我们需要判断其标签内部是否还内嵌了其他标签,这里我们可以通过递归的方式来实现。

完整代码如下:

const XMLToJSON = (XMLData = "") => {
  let ind = 0,
    obj = {};
  const XMLParse = (obj = {}) => {
    let value = "",
      tag = "";
    //遍历xml字符串
    while (ind < XMLData.length) {
      //正在遍历标签内的值,记录标签名
      if (XMLData[ind] === "<" || (tag && XMLData[ind] !== ">")) {
        if (XMLData[ind] === "<") ind++;
        tag += XMLData[ind];
      } else if (XMLData[ind] === ">") {
        //获取到完整的标签名
        //通过rowNum属性来判断是否为数组,有rowNum属性的即为数组
        const rowNum = tag.split(" ")[1] || "";
        tag = tag.split(" ")[0];
        if (tag[0] !== "/") {
          ind++;
          //同一层级当前标签为出现过且不包含rowNum属性,将其处理为对象
          if (obj[tag] === undefined && !rowNum.includes("rowNum")) {
            obj[tag] = {};
            const val = XMLParse(obj[tag], [tag]);
            if (val) obj[tag] = val;
            if (JSON.stringify(obj[tag]) === "{}") {
              obj[tag] = "";
            }
          } else {
            //同一层级下拥有多个同名标签或包含rowNum属性,将其处理为数组
            if (obj[tag] === undefined) obj[tag] = [{}];
            else if (Array.isArray(obj[tag])) obj[tag].push({});
            else obj[tag] = [obj[tag], {}];
            const objInd = obj[tag].length - 1;
            //递归处理标签内的嵌套标签或提取值
            const val = XMLParse(obj[tag][objInd]);
            //有标签值的直接赋值,如:<a>111</a> -> {a:111}
            if (val) obj[tag][objInd] = val;
            //无子节点的赋空值
            if (JSON.stringify(obj[tag][objInd]) === "{}") {
              obj[tag][objInd] = "";
            }
          }
        } else {
          //闭合标签,结束递归返回获取到的值
          return value;
        }
        tag = "";
        value = "";
      } else {
        value += XMLData[ind];
      }
      ind++;
    }
  };
  XMLParse(obj);
  return obj;
};

当然,有 XML 转 JSON 的话,那 JSON 不得也要准备上:

JSONToXML = (JSONData = {}) => {
  if (!JSONData) return "";
  let res = "";
  const JSONParse = (obj) => {
    for (const key in obj) {
      if (Array.isArray(obj[key])) {
        obj[key].forEach((item, index) => {
          res += `<${key} rowNum="${index}">${item}</${key}>`;
        });
      } else if (typeof obj[key] === "object") {
        res += `<${key}>`;
        JSONParse(obj[key]);
        res += `</${key}>`;
      } else {
        res += `<${key}>${obj[key] || ""}</${key}>`;
      }
    }
  };
  JSONParse(JSONData);
  return res;
};
  • 跑下试试
const xmlData =
  '<res><a></a><b></b><c><d rowNum="0">111</d></c><e>222</e></res>';
const jsonData = {
  res: {
    a: "",
    b: "",
    c: {
      d: ["111"],
    },
    e: "222",
  },
};
console.log(XMLToJSON(xmlData));
console.log(JSONToXML(jsonData));

结果如下图:

image.png

写到这里是不是就该结束了?不,写完这个方法之后我才发现原来现在浏览器都有内建的 XML 解析器。XML 解析器可以把 XML 文档转换为 XML DOM 对象 - 可通过 JavaScript 操作的对象。

XML 解析器

  • Internet Explorer 使用 loadXML() 方法来解析 XML 字符串,而其他浏览器使用 DOMParser 对象。
const xmlData =
  '<res><a></a><b></b><c><d rowNum="0">111</d></c><e>222</e></res>';
if (window.DOMParser) {
  parser = new DOMParser();
  xmlDoc = parser.parseFromString(xmlData, "text/xml");
} // Internet Explorer
else {
  xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
  xmlDoc.async = false;
  xmlDoc.loadXML(xmlData);
}

结果如下图:

image.png

这样的话我们可以利用浏览器自带的 XML 解析器来完成 XML 数据的转换:

const deepParse = (xmlDoc, tagName) => {
  const xmlObj = {};
  const rootDom = xmlDoc.getElementsByTagName(tagName)[0];
  for (let i = 0; i < rootDom.children.length; i++) {
    const child = rootDom.children[i];
    if (child.children.length > 0) {
      xmlObj[child.nodeName] = deepParse(xmlDoc, child.nodeName);
    } else {
      if (xmlObj[child.nodeName] !== undefined) {
        if (!Array.isArray(xmlObj[child.nodeName])) {
          xmlObj[child.nodeName] = [xmlObj[child.nodeName]];
        }
        xmlObj[child.nodeName].push(child.textContent);
      } else {
        xmlObj[child.nodeName] = child.textContent;
      }
    }
  }
  return xmlObj;
};
const parseXmlData = (xmlStr, tagName = "rtInfo") => {
  const parser = new DOMParser();
  const xmlDoc = parser.parseFromString(xmlStr, "text/xml");
  const xmlObj = deepParse(xmlDoc, tagName);
  return xmlObj;
};

结果如下图:

image.png

说在后面

🎉 这里是 JYeontu,现在是一名前端工程师,有空会刷刷算法题,平时喜欢打羽毛球 🏸 ,平时也喜欢写些东西,既为自己记录 📋,也希望可以对大家有那么一丢丢的帮助,写的不好望多多谅解 🙇,写错的地方望指出,定会认真改进 😊,在此谢谢大家的支持,我们下文再见 🙌。

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐