在Emacs上美化打印XML文件

88

我使用emacs编辑我的xml文件(nxml-mode),由机器生成的文件没有任何标记的漂亮格式。

我搜索了整个文件的漂亮打印和缩进保存的方法,但没有找到自动的方法。

有没有办法?或者至少在Linux上有一些可以做到的编辑器。

15个回答

118

你甚至不需要编写自己的函数 - sgml-mode (gnu emacs 的一个核心模块) 内置了一个称为 (sgml-pretty-print ...) 的漂亮打印函数,它接受区域开始和结束参数。

如果你在剪切和粘贴 xml 时发现终端会在任意位置截断行,你可以使用这个漂亮打印工具先修复破损的行。


1
(sgml-pretty-print(region-beginning)(region-end)) - ScootyPuff
8
我不确定sgml-mode随着时间的推移可能会有哪些变化。今天,我通过执行C-x C-f foo.xmlM-x sgml-modeM-x sgml-pretty-print,对我的XML文件进行了漂亮的打印。在漂亮的打印之前,它是一个单行文件,在漂亮的打印之后则有720行。(不过,在完成之前,Emacs挂起了二十秒或更长时间。) - daveloyall
1
实际上,我也需要执行 C-x g 命令以选择整个缓冲区作为一个区域。 - daveloyall
3
我甚至不需要切换到sgml-mode。在nXML模式下,这是一个M-x命令! - nroose
2
使用 Emacs 26.2,我可以保持在 nXML 模式下,选择整个缓冲区 C-x h 然后 M-x sgml-pretty-print。现在 XML 将被漂亮地格式化。 - Swedgin
显示剩余2条评论

89

如果你只需要漂亮的缩进而不引入任何新的换行符,你可以使用这些按键将indent-region命令应用于整个缓冲区:

C-x h
C-M-\

如果您也需要引入换行,以便开放和关闭标签在不同的行上,您可以使用以下非常好的elisp函数,由Benjamin Ferrari编写。我在他的博客上找到它,并希望在这里复制它没有问题:
(defun bf-pretty-print-xml-region (begin end)
  "Pretty format XML markup in region. You need to have nxml-mode
http://www.emacswiki.org/cgi-bin/wiki/NxmlMode installed to do
this.  The function inserts linebreaks to separate tags that have
nothing but whitespace between them.  It then indents the markup
by using nxml's indentation rules."
  (interactive "r")
  (save-excursion
    (nxml-mode)
    (goto-char begin)
    (while (search-forward-regexp "\>[ \\t]*\<" nil t) 
      (backward-char) (insert "\n") (setq end (1+ end)))
    (indent-region begin end))
  (message "Ah, much better!"))

这不依赖于像Tidy这样的外部工具。

1
好的defun,谢谢。从上面漂亮的打印defun中删除(nxml-mode)允许它在emacs 22.2.1内置的sgml-mode中工作。但我修改了它以执行整个缓冲区(point-min)到(point-max),因为那是我的主要任务。还有一个错误:对于每个插入的换行符,您需要增加end。 - Cheeso
我该如何在Emacs中使用这个函数?我已经将函数代码复制并粘贴到scratch缓冲区中并进行了评估。现在,我该如何调用这个函数? - Alexandre Rademaker
1
评估完defun之后,您可以像调用其他函数一样调用它:M-x bf-pretty-print-xml-region。(当然,您不必输入全部内容,使用Tab键自动完成应该就足够了。)您可能不想每次使用时都定义该函数,所以将其放在启动时加载的某个位置,例如~/.emacs.d/init.el文件中。 - Christian Berg
1
如何拆分长属性列表? - ceving
这太棒了,因为Tidy会抱怨无效的字符编码,并要求我在重新格式化文件之前清理它们!有时候我们只是想看到一个损坏的XML文件的结构,而Tidy会拒绝帮助。 - TauPan
对于每个 (insert "\n"),您还需要将 end 增加 1,以便缩进整个区域,否则可能会错过最后几行。此更正已添加到本答案中提供的 Benjamin Ferrari 博客链接中。 - Kind Stranger

35

Emacs 可以通过 M-| 运行任意命令。如果您已经安装了 xmllint:

"M-| xmllint --format -" 将格式化所选区域

"C-u M-| xmllint --format -" 将执行同样的操作,但替换所选区域为输出结果


使用 M-x mark-whole-buffer 命令来标记整个缓冲区内容作为要处理的区域。 - Harald
在Doom Emacs中,该命令似乎是M-!。 - Ishmael7

25

到2013年底,tidy.el版本:20111222.1756无法在Emacs 24上运行,并显示错误信息wrong type argument: stringp, nil - keiw
@keiw 这可能是因为您正在使用没有文件名的缓冲区进行操作。我也遇到了同样的错误,并追踪到至少在我的一侧是这个原因。 - Alf

22

用于插入换行符并进行格式化打印

M-x sgml-mode
M-x sgml-pretty-print

20

多亏了上面的Tim Helmstedt,我做出了以下的样式:

(defun nxml-pretty-format ()
    (interactive)
    (save-excursion
        (shell-command-on-region (point-min) (point-max) "xmllint --format -" (buffer-name) t)
        (nxml-mode)
        (indent-region begin end)))

快速且简单。非常感谢。

3
这个命令在GNU Emacs 24上出错了,所以我把最后一行改成了:(indent-region 0 (count-lines (point-min) (point-max))) - John J. Camilleri

8

这是我对Benjamin Ferrari版本进行的一些调整:

  • search-forward-regexp没有指定结束位置,因此它会在区域开头到缓冲区结尾(而不是区域结尾)操作。
  • 现在增加了end的值,正如Cheeso所指出的那样。
  • 它会在<tag></tag>之间插入一个断点,从而修改其值。是的,技术上我们在这里修改了所有内容的值,但空的开始/结束更有可能是重要的。现在使用两个单独的、稍微更严格的搜索来避免这种情况。

仍然具有“不依赖外部整洁”等特点。但是,它需要cl来支持incf宏。

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; pretty print xml region
(defun pretty-print-xml-region (begin end)
  "Pretty format XML markup in region. You need to have nxml-mode
http://www.emacswiki.org/cgi-bin/wiki/NxmlMode installed to do
this.  The function inserts linebreaks to separate tags that have
nothing but whitespace between them.  It then indents the markup
by using nxml's indentation rules."
  (interactive "r")
  (save-excursion
    (nxml-mode)
    (goto-char begin)
    ;; split <foo><foo> or </foo><foo>, but not <foo></foo>
    (while (search-forward-regexp ">[ \t]*<[^/]" end t)
      (backward-char 2) (insert "\n") (incf end))
    ;; split <foo/></foo> and </foo></foo>
    (goto-char begin)
    (while (search-forward-regexp "<.*?/.*?>[ \t]*<" end t)
      (backward-char) (insert "\n") (incf end))
    (indent-region begin end nil)
    (normal-mode))
  (message "All indented!"))

5

一种做法是,如果你有以下格式的东西

<abc>     <abc><abc>   <abc></abc> </abc></abc>       </abc>

在Emacs中,尝试使用:
M-x nxml-mode
M-x replace-regexp RET  > *< RET >C-q C-j< RET 
C-M-\ to indent

这将把上面的XML示例缩进到下面。
<abc>
  <abc>
    <abc>
      <abc>
      </abc>
    </abc>
  </abc>
</abc>

在VIM中,您可以通过以下方式实现此目标:
:set ft=xml
:%s/>\s*</>\r</g
ggVG=

希望这能帮到您。

3
截至2017年,Emacs已经默认具备了这种功能,但您需要将以下小函数写入~/.emacs.d/init.el文件中:
(require 'sgml-mode)

(defun reformat-xml ()
  (interactive)
  (save-excursion
    (sgml-pretty-print (point-min) (point-max))
    (indent-region (point-min) (point-max))))

然后只需调用M-x reformat-xml

来源:https://davidcapello.com/blog/emacs/reformat-xml-on-emacs/


1
应该只接受“翻译文本内容”的答案,因为仅使用sgml-pretty-print无法完成任务。 - sandwood

2

我采用了Jason Viers的版本,并添加了逻辑来将xmlns声明放在自己的行上。这假设你有xmlns=和xmlns:之间没有空格。

(defun cheeso-pretty-print-xml-region (begin end)
  "Pretty format XML markup in region. You need to have nxml-mode
http://www.emacswiki.org/cgi-bin/wiki/NxmlMode installed to do
this.  The function inserts linebreaks to separate tags that have
nothing but whitespace between them.  It then indents the markup
by using nxml's indentation rules."
  (interactive "r")
  (save-excursion
    (nxml-mode)
    ;; split <foo><bar> or </foo><bar>, but not <foo></foo>
    (goto-char begin)
    (while (search-forward-regexp ">[ \t]*<[^/]" end t)
      (backward-char 2) (insert "\n") (incf end))
    ;; split <foo/></foo> and </foo></foo>
    (goto-char begin)
    (while (search-forward-regexp "<.*?/.*?>[ \t]*<" end t)
      (backward-char) (insert "\n") (incf end))
    ;; put xml namespace decls on newline
    (goto-char begin)
    (while (search-forward-regexp "\\(<\\([a-zA-Z][-:A-Za-z0-9]*\\)\\|['\"]\\) \\(xmlns[=:]\\)" end t)
      (goto-char (match-end 0))
      (backward-char 6) (insert "\n") (incf end))
    (indent-region begin end nil)
    (normal-mode))
  (message "All indented!"))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接