Довольно печатать XML с помощью JavaScript

У меня есть строка, представляющая XML-код без отступов, который я хотел бы распечатать. Например:

<root><node/></root>

должен стать:

<root>
  <node/>
</root>

Подсветка синтаксиса не является обязательной. Чтобы решить эту проблему, я сначала преобразую XML, чтобы добавить возврат каретки и пробелы, а затем использую предварительный тег для вывода XML. Чтобы добавить новые строки и пробелы, я написал следующую функцию:

function formatXml(xml) {
    var formatted = '';
    var reg = /(>)(<)(\/*)/g;
    xml = xml.replace(reg, '$1\r\n$2$3');
    var pad = 0;
    jQuery.each(xml.split('\r\n'), function(index, node) {
        var indent = 0;
        if (node.match( /.+<\/\w[^>]*>$/ )) {
            indent = 0;
        } else if (node.match( /^<\/\w/ )) {
            if (pad != 0) {
                pad -= 1;
            }
        } else if (node.match( /^<\w[^>]*[^\/]>.*$/ )) {
            indent = 1;
        } else {
            indent = 0;
        }

        var padding = '';
        for (var i = 0; i < pad; i++) {
            padding += '  ';
        }

        formatted += padding + node + '\r\n';
        pad += indent;
    });

    return formatted;
}

Затем я вызываю функцию следующим образом:

jQuery('pre.formatted-xml').text(formatXml('<root><node1/></root>'));

Это прекрасно работает для меня, но пока я писал предыдущую функцию, я думал, что должен быть лучший способ. Итак, мой вопрос: знаете ли вы какой-нибудь лучший способ дать XML-строку, чтобы красиво распечатать ее на HTML-странице? Любые JavaScript-фреймворки и / или плагины, которые могут сделать эту работу, приветствуются. Мое единственное требование - это сделать на стороне клиента.

25 ответов

Решение

Из текста вопроса у меня складывается впечатление, что ожидается строковый результат, а не результат в формате HTML.

Если это так, самый простой способ достичь этого - обработать XML-документ с помощью преобразования идентификаторов и<xsl:output indent="yes"/>инструкция:


 

    
      
        
      
    

При применении этого преобразования к предоставленному документу XML:

<Корень><узел />

большинство процессоров XSLT (.NET XslCompiledTransform, Saxon 6.5.4 и Saxon 9.0.0.2, AltovaXML) дают желаемый результат:

<Корень>
  <узел />

Это можно сделать с помощью встроенных инструментов javascript, без сторонних библиотек, расширяя ответ @Dimitre Novatchev:

var prettifyXml = function(sourceXml)
{
    var xmlDoc = new DOMParser().parseFromString(sourceXml, 'application/xml');
    var xsltDoc = new DOMParser().parseFromString([
        // describes how we want to modify the XML - indent everything
        '<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform">',
        '  <xsl:strip-space elements="*"/>',
        '  <xsl:template match="para[content-style][not(text())]">', // change to just text() to strip space in text nodes
        '    <xsl:value-of select="normalize-space(.)"/>',
        '  </xsl:template>',
        '  <xsl:template match="node()|@*">',
        '    <xsl:copy><xsl:apply-templates select="node()|@*"/></xsl:copy>',
        '  </xsl:template>',
        '  <xsl:output indent="yes"/>',
        '</xsl:stylesheet>',
    ].join('\n'), 'application/xml');

    var xsltProcessor = new XSLTProcessor();    
    xsltProcessor.importStylesheet(xsltDoc);
    var resultDoc = xsltProcessor.transformToDocument(xmlDoc);
    var resultXml = new XMLSerializer().serializeToString(resultDoc);
    return resultXml;
};

console.log(prettifyXml('<root><node/></root>'));

Выходы:

<root>
  <node/>
</root>

JSFiddle

Рассмотреть возможность использования плагина vkBeautify

http://www.eslinstructor.net/vkbeautify/

он написан на простом javascript, очень маленький: менее 1,5 КБ, если минимизирован, очень быстрый: менее 5 мс. обрабатывать 50K XML-текст.

Нашел этот поток, когда у меня было похожее требование, но я упростил код OP следующим образом:

function formatXml(xml, tab) { // tab = optional indent value, default is tab (\t)
    var formatted = '', indent= '';
    tab = tab || '\t';
    xml.split(/>\s*</).forEach(function(node) {
        if (node.match( /^\/\w/ )) indent = indent.substring(tab.length); // decrease indent by one 'tab'
        formatted += indent + '<' + node + '>\r\n';
        if (node.match( /^<?\w[^>]*[^\/]$/ )) indent += tab;              // increase indent
    });
    return formatted.substring(1, formatted.length-3);
}

работает для меня!

Небольшая модификация функции javascript в efnx clckclcks. Я изменил форматирование с пробелов на табуляцию, но самое главное, я позволил тексту остаться в одной строке:

var formatXml = this.formatXml = function (xml) {
        var reg = /(>)\s*(<)(\/*)/g; // updated Mar 30, 2015
        var wsexp = / *(.*) +\n/g;
        var contexp = /(<.+>)(.+\n)/g;
        xml = xml.replace(reg, '$1\n$2$3').replace(wsexp, '$1\n').replace(contexp, '$1\n$2');
        var pad = 0;
        var formatted = '';
        var lines = xml.split('\n');
        var indent = 0;
        var lastType = 'other';
        // 4 types of tags - single, closing, opening, other (text, doctype, comment) - 4*4 = 16 transitions 
        var transitions = {
            'single->single': 0,
            'single->closing': -1,
            'single->opening': 0,
            'single->other': 0,
            'closing->single': 0,
            'closing->closing': -1,
            'closing->opening': 0,
            'closing->other': 0,
            'opening->single': 1,
            'opening->closing': 0,
            'opening->opening': 1,
            'opening->other': 1,
            'other->single': 0,
            'other->closing': -1,
            'other->opening': 0,
            'other->other': 0
        };

        for (var i = 0; i < lines.length; i++) {
            var ln = lines[i];

            // Luca Viggiani 2017-07-03: handle optional <?xml ... ?> declaration
            if (ln.match(/\s*<\?xml/)) {
                formatted += ln + "\n";
                continue;
            }
            // ---

            var single = Boolean(ln.match(/<.+\/>/)); // is this line a single tag? ex. <br />
            var closing = Boolean(ln.match(/<\/.+>/)); // is this a closing tag? ex. </a>
            var opening = Boolean(ln.match(/<[^!].*>/)); // is this even a tag (that's not <!something>)
            var type = single ? 'single' : closing ? 'closing' : opening ? 'opening' : 'other';
            var fromTo = lastType + '->' + type;
            lastType = type;
            var padding = '';

            indent += transitions[fromTo];
            for (var j = 0; j < indent; j++) {
                padding += '\t';
            }
            if (fromTo == 'opening->closing')
                formatted = formatted.substr(0, formatted.length - 1) + ln + '\n'; // substr removes line break (\n) from prev loop
            else
                formatted += padding + ln + '\n';
        }

        return formatted;
    };

Personnaly, я использую https://github.com/google/code-prettify с этой функцией:

prettyPrintOne('<root><node1><root>', 'xml')

Или, если вам просто нужна другая функция js, я модифицировал Darin (очень):

var formatXml = this.formatXml = function (xml) {
    var reg = /(>)(<)(\/*)/g;
    var wsexp = / *(.*) +\n/g;
    var contexp = /(<.+>)(.+\n)/g;
    xml = xml.replace(reg, '$1\n$2$3').replace(wsexp, '$1\n').replace(contexp, '$1\n$2');
    var pad = 0;
    var formatted = '';
    var lines = xml.split('\n');
    var indent = 0;
    var lastType = 'other';
    // 4 types of tags - single, closing, opening, other (text, doctype, comment) - 4*4 = 16 transitions 
    var transitions = {
        'single->single'    : 0,
        'single->closing'   : -1,
        'single->opening'   : 0,
        'single->other'     : 0,
        'closing->single'   : 0,
        'closing->closing'  : -1,
        'closing->opening'  : 0,
        'closing->other'    : 0,
        'opening->single'   : 1,
        'opening->closing'  : 0, 
        'opening->opening'  : 1,
        'opening->other'    : 1,
        'other->single'     : 0,
        'other->closing'    : -1,
        'other->opening'    : 0,
        'other->other'      : 0
    };

    for (var i=0; i < lines.length; i++) {
        var ln = lines[i];
        var single = Boolean(ln.match(/<.+\/>/)); // is this line a single tag? ex. <br />
        var closing = Boolean(ln.match(/<\/.+>/)); // is this a closing tag? ex. </a>
        var opening = Boolean(ln.match(/<[^!].*>/)); // is this even a tag (that's not <!something>)
        var type = single ? 'single' : closing ? 'closing' : opening ? 'opening' : 'other';
        var fromTo = lastType + '->' + type;
        lastType = type;
        var padding = '';

        indent += transitions[fromTo];
        for (var j = 0; j < indent; j++) {
            padding += '    ';
        }

        formatted += padding + ln + '\n';
    }

    return formatted;
};

Эта библиотека делает именно то, что вы хотите!

https://code.google.com/p/vkbeautify/

Все приведенные здесь функции javascript не будут работать для документа xml с неопределенными пробелами между конечным тегом ">" и начальным тегом "<". Чтобы их исправить, нужно просто заменить первую строку в функциях

var reg = /(>)(<)(\/*)/g;

от

var reg = /(>)\s*(<)(\/*)/g;

В текущем проекте мне нужно было преобразить и раскрасить XML без дополнительных библиотек. Следующий автономный код работает достаточно хорошо.

      function formatXml(xml,colorize,indent) { 
  function esc(s){return s.replace(/[-\/&<> ]/g,function(c){          // Escape special chars
    return c==' '?'&nbsp;':'&#'+c.charCodeAt(0)+';';});}            
  var sm='<div id="xmt">',se='<div id="xel">',sd='<div id="xdt">',
      sa='<div id="xat">',tb='<div id="xtb">',tc='<div id="xtc">',
      id=indent||'  ',sz=tz='</div>',re=is='',ib,ob,at,i;
  if (!colorize) sm=se=sd=sa=sz='';   
  xml.slice(1,-1).split(/>\s*</).forEach(function(nd){
    ob=('<'+nd+'>').match(/^(<[!?\/]?)(.*?)([?\/]?>)$/);              // Split outer brackets
    ib=ob[2].match(/^(.*?)>(.*)<\/(.*)$/)||['',ob[2],''];             // Split inner brackets 
    at=ib[1].match(/^--.*--$|=|('|").*?\1|[^\t\n\f \/>"'=]+/g)||['']; // Split attributes
    if (ob[1]=='</') is=is.substring(id.length);                      // Decrease indent
    re+=tb+tc+esc(is)+tz+tc+sm+esc(ob[1])+sz+se+esc(at[0])+sz;
    for (i=1;i<at.length;i++) re+=(at[i]=="="?sm+"="+sz+sd+esc(at[++i]):sa+' '+at[i])+sz;
    re+=ib[2]?sm+esc('>')+sz+sd+esc(ib[2])+sz+sm+esc('</')+sz+se+ib[3]+sz:'';
    re+=sm+esc(ob[3])+sz+tz+tz;
    if (ob[1]+ob[3]+ib[2]=='<>') is+=id;                              // Increase indent
  });
  return re;
}

См. Https://jsfiddle.net/3n0cazL8/

Если вы ищете решение JavaScript, просто возьмите код из инструмента Pretty Diff по адресу http://prettydiff.com/?m=beautify

Вы также можете отправлять файлы в инструмент с помощью параметра s, например: http://prettydiff.com/?m=beautify&s=https://stackru.com/

Как насчет создания узла-заглушки (document.createElement('div') - или использования вашего библиотечного эквивалента), заполнения его строкой xml (через innerHTML) и вызова простой рекурсивной функции для корневого элемента / или элемента-заглушки на случай, если вы нет рута Функция будет вызывать себя для всех дочерних узлов.

Затем вы можете выделить синтаксис по пути, быть уверенным, что разметка правильно сформирована (это делается автоматически браузером при добавлении через innerHTML) и т. Д. Это не будет так много кода и, вероятно, достаточно быстро.

Вы можете получить довольно отформатированный XML с помощью XML-украшения

var prettyXmlText = new XmlBeautify().beautify(xmlText, 
                    {indent: "  ",useSelfClosingElement: true});

отступ: шаблон отступа, как пробелы

useSelfClosingElement: true => использовать самозакрывающийся элемент, когда пустой элемент.

JSFiddle

Original (Перед)

<?xml version="1.0" encoding="utf-8"?><example version="2.0">
  <head><title>Original aTitle</title></head>
  <body info="none" ></body>
</example>

Приукрашена (После)

<?xml version="1.0" encoding="utf-8"?>
<example version="2.0">
  <head>
    <title>Original aTitle</title>
  </head>
  <body info="none" />
</example>
Or just print out the special HTML characters?

Ex: <xmlstuff>&#10; &#09;<node />&#10;</xmlstuff>   


&#09;   Horizontal tab  
&#10;   Line feed

Вот еще одна функция для форматирования XML

function formatXml(xml){
    var out = "";
    var tab = "    ";
    var indent = 0;
    var inClosingTag=false;
    var dent=function(no){
        out += "\n";
        for(var i=0; i < no; i++)
            out+=tab;
    }


    for (var i=0; i < xml.length; i++) {
        var c = xml.charAt(i);
        if(c=='<'){
            // handle </
            if(xml.charAt(i+1) == '/'){
                inClosingTag = true;
                dent(--indent);
            }
            out+=c;
        }else if(c=='>'){
            out+=c;
            // handle />
            if(xml.charAt(i-1) == '/'){
                out+="\n";
                //dent(--indent)
            }else{
              if(!inClosingTag)
                dent(++indent);
              else{
                out+="\n";
                inClosingTag=false;
              }
            }
        }else{
          out+=c;
        }
    }
    return out;
}
var formatXml = this.formatXml = function (xml) {
        var reg = /(>)(<)(\/*)/g;
        var wsexp = / *(.*) +\n/g;
        var contexp = /(<.+>)(.+\n)/g;
        xml = xml.replace(reg, '$1\n$2$3').replace(wsexp, '$1\n').replace(contexp, '$1\n$2');
        var pad = 0;
        var formatted = '';
        var lines = xml.split('\n');
        var indent = 0;
        var lastType = 'other';

Форматирование XML может быть выполнено путем синтаксического анализа XML, добавления или изменения текстовых узлов в дереве dom для отступов, а затем сериализации DOM обратно в xml.

Пожалуйста, проверьте функцию formatxml в https://jsonbrowser.sourceforge.io/formatxml.js. Вы можете увидеть функцию в действии на https://jsonbrowser.sourceforge.io/ на вкладке Xml.

Ниже приведен упрощенный код. formatxml.js добавляет проверку ошибок, необязательное удаление комментариев, отступ в качестве параметра и обрабатывает непространственный текст между родительскими узлами.

      const parser = new DOMParser();

const serializer = new XMLSerializer();

function formatXml(xml) {
  let xmlDoc = parser.parseFromString(xml, 'application/xml');
  let rootElement = xmlDoc.documentElement;
  indentChildren(xmlDoc, rootElement, "\n", "\n  ");
  xml = serializer.serializeToString(xmlDoc);
  return xml;
}

function indentChildren(xmlDoc, node, prevPrefix, prefix) {
  let children = node.childNodes;
  let i;
  let prevChild = null;
  let prevChildType = 1;
  let child = null;
  let childType;
  for (i = 0; i < children.length; i++) {
    child = children[i];
    childType = child.nodeType;
    if (childType != 3) {
      if (prevChildType == 3) {
        // Update prev text node with correct indent
        prevChild.nodeValue = prefix;
      } else {
        // Create and insert text node with correct indent
        let textNode = xmlDoc.createTextNode(prefix);
        node.insertBefore(textNode, child);
        i++;
      }
      if (childType == 1) {
        let isLeaf = child.childNodes.length == 0 || child.childNodes.length == 1 && child.childNodes[0].nodeType != 1;
        if (!isLeaf) {
          indentChildren(xmlDoc, child, prefix, prefix + "  ");
        }
      }
    }
    prevChild = child;
    prevChildType =childType;
  }
  if (child != null) {
    // Previous level indentation after last child
    if (childType == 3) {
      child.nodeValue = prevPrefix;
    } else {
      let textNode = xmlDoc.createTextNode(prevPrefix);
      node.append(textNode);
    }
  }
}

Ссылка: https://www.w3schools.com/XML/dom_intro.asp

XMLSpectrum форматирует XML, поддерживает отступы атрибутов, а также делает подсветку синтаксиса для XML и любых встроенных выражений XPath:

XMLSpectrum отформатированный XML

XMLSpectrum - это проект с открытым исходным кодом, закодированный в XSLT 2.0 - поэтому вы можете запускать эту сторону сервера с таким процессором, как Saxon-HE (рекомендуется) или со стороны клиента, используя Saxon-CE.

XMLSpectrum еще не оптимизирован для работы в браузере, поэтому рекомендуется запускать его на стороне сервера.

Используйте приведенный выше метод для красивой печати, а затем добавьте его в любой div с помощью метода jquery text (). например, идентификатор div xmldiv затем используйте:

$("#xmldiv").text(formatXml(youXmlString));

var reg = /(>)\s*(<)(\/*)/g;
xml = xml.replace(/\r|\n/g, ''); //deleting already existing whitespaces
xml = xml.replace(reg, '$1\r\n$2$3');

Это может включать создание узлов как объектов, но вы можете полностью контролировать экспорт хорошо отформатированного xml.

Следующее будет возвращать строковый массив строк, которые вы можете объединить с помощью нового разделителя строк "\ n".

      /**
 * The child of an XML node can be raw text or another xml node.
 */
export type PossibleNode = XmlNode | string;

/**
 * Base XML Node type.
 */
export interface XmlNode {
  tag: string;
  attrs?: { [key: string]: string };
  children?: PossibleNode[];
}

/**
 * Exports the given XML node to a string array.
 * 
 * @param node XML Node
 * @param autoClose Auto close the tag
 * @param indent Indentation level
 * @returns String array
 */
export function xmlNodeToString(
  node: XmlNode,
  autoClose: boolean = true,
  indent: number = 0
): string[] {
  const indentStr = " ".repeat(indent);
  const sb: string[] = [];
  sb.push(`${indentStr}<${node.tag}`);
  if (node.attrs) {
    for (const key in node.attrs) {
      sb.push(`${indentStr} ${key}="${node.attrs[key]}"`);
    }
  }
  if (node.children) {
    if (node.children.length === 1 && typeof node.children[0] === "string") {
      sb[sb.length - 1] += ">" + node.children[0];
    } else {
      sb.push(`${indentStr}>`);
      for (const child of node.children) {
        if (typeof child === "string") {
          sb.push(`${indentStr}  ${child}`);
        } else {
          const lines = xmlNodeToString(child, autoClose, indent + 1);
          sb.push(...lines.map((line) => `${indentStr}  ${line}`));
        }
      }
    }
    if (autoClose) {
      if (node.children.length === 1 && typeof node.children[0] === "string") {
        sb[sb.length - 1] += `</${node.tag}>`;
      } else {
        sb.push(`${indentStr}</${node.tag}>`);
      }
    }
  } else {
    if (autoClose) {
      sb.push(`${indentStr}/>`);
    } else {
      sb.push(`${indentStr}>`);
    }
  }
  return sb;
}

Обновления приветствуются по сути: https://gist.github.com/rodydavis/acd609560ab0416b60681fddabc43eee

Вы также можете использовать клиентскую часть Saxon-JS:

      <script src="SaxonJS/SaxonJS2.js"></script>

<script>
let myXML = `<root><node/></root>`;

SaxonJS.getResource({
   text: myXML.replace(`xml:space="preserve"`, ''),
   type: "xml"
}).then(doc => {
   const output = SaxonJS.serialize(doc, {method: "xml", indent: true, "omit-xml-declaration":true});
   console.log(output);
})
</script>

Установкастороне клиента
Saxon-JS наСтраница загрузки Saxon-JS

https://www.npmjs.com/package/js-beautify

Эта библиотека работает для меня. Поддерживает вкладку, поддерживает веб и версию узла. Также поддерживает JS, HTML, CSS. Также доступно как CDN.

Эта моя версия, может быть, полезна для других, использующая String Builder Видела, что у кого-то был такой же фрагмент кода.

    public String FormatXml(String xml, String tab)
    {
        var sb = new StringBuilder();
        int indent = 0;
        // find all elements
        foreach (string node in Regex.Split(xml,@">\s*<"))
        {
            // if at end, lower indent
            if (Regex.IsMatch(node, @"^\/\w")) indent--;
            sb.AppendLine(String.Format("{0}<{1}>", string.Concat(Enumerable.Repeat(tab, indent).ToArray()), node));
            // if at start, increase indent
            if (Regex.IsMatch(node, @"^<?\w[^>]*[^\/]$")) indent++;
        }
        // correct first < and last > from the output
        String result = sb.ToString().Substring(1);
        return result.Remove(result.Length - Environment.NewLine.Length-1);
    }

Библиотека xml-to-json имеет методformatXml(xml). Я сопровождаю проект.

var prettyXml = formatXml("<a><b/></a>");

// <a>
//   <b/>
// </a>
Другие вопросы по тегам