rng-nxml.el 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565
  1. ;;; rng-nxml.el --- make nxml-mode take advantage of rng-validate-mode -*- lexical-binding:t -*-
  2. ;; Copyright (C) 2003, 2007-2017 Free Software Foundation, Inc.
  3. ;; Author: James Clark
  4. ;; Keywords: wp, hypermedia, languages, XML, RelaxNG
  5. ;; This file is part of GNU Emacs.
  6. ;; GNU Emacs is free software: you can redistribute it and/or modify
  7. ;; it under the terms of the GNU General Public License as published by
  8. ;; the Free Software Foundation, either version 3 of the License, or
  9. ;; (at your option) any later version.
  10. ;; GNU Emacs is distributed in the hope that it will be useful,
  11. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. ;; GNU General Public License for more details.
  14. ;; You should have received a copy of the GNU General Public License
  15. ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
  16. ;;; Commentary:
  17. ;;; Code:
  18. (require 'easymenu)
  19. (require 'xmltok)
  20. (require 'nxml-util)
  21. (require 'nxml-ns)
  22. (require 'rng-match)
  23. (require 'rng-util)
  24. (require 'rng-valid)
  25. (require 'nxml-mode)
  26. (require 'rng-loc)
  27. (require 'sgml-mode)
  28. (defcustom rng-nxml-auto-validate-flag t
  29. "Non-nil means automatically turn on validation with nxml-mode."
  30. :type 'boolean
  31. :group 'relax-ng)
  32. (defcustom rng-preferred-prefix-alist
  33. '(("http://www.w3.org/1999/XSL/Transform" . "xsl")
  34. ("http://www.w3.org/1999/02/22-rdf-syntax-ns#" . "rdf")
  35. ("http://www.w3.org/1999/xlink" . "xlink")
  36. ("http://www.w3.org/2001/XmlSchema" . "xsd")
  37. ("http://www.w3.org/2001/XMLSchema-instance" . "xsi")
  38. ("http://purl.org/dc/elements/1.1/" . "dc")
  39. ("http://purl.org/dc/terms/" . "dcterms"))
  40. "Alist of namespaces vs preferred prefixes."
  41. :type '(repeat (cons :tag "With"
  42. (string :tag "this namespace URI")
  43. (string :tag "use this prefix")))
  44. :group 'relax-ng)
  45. (defvar rng-complete-end-tags-after-< t
  46. "Non-nil means immediately after < complete on end-tag names.
  47. Complete on start-tag names regardless.")
  48. (defvar rng-nxml-easy-menu
  49. '("XML"
  50. ["Show Outline Only" nxml-hide-all-text-content]
  51. ["Show Everything" nxml-show-all]
  52. "---"
  53. ["Validation" rng-validate-mode
  54. :style toggle
  55. :selected rng-validate-mode]
  56. ["Electric Pairs" sgml-electric-tag-pair-mode
  57. :style toggle
  58. :selected sgml-electric-tag-pair-mode]
  59. "---"
  60. ("Set Schema"
  61. ["Automatically" rng-auto-set-schema]
  62. ("For Document Type"
  63. :filter (lambda (menu)
  64. (mapcar (lambda (type-id)
  65. (vector type-id
  66. (list 'rng-set-document-type
  67. type-id)))
  68. (rng-possible-type-ids))))
  69. ["Any Well-Formed XML" rng-set-vacuous-schema]
  70. ["File..." rng-set-schema-file])
  71. ["Show Schema Location" rng-what-schema]
  72. ["Save Schema Location" rng-save-schema-location :help
  73. "Save the location of the schema currently being used for this buffer"]
  74. "---"
  75. ["First Error" rng-first-error :active rng-validate-mode]
  76. ["Next Error" rng-next-error :active rng-validate-mode]
  77. "---"
  78. ["Customize nXML" (customize-group 'nxml)]))
  79. ;;;###autoload
  80. (defun rng-nxml-mode-init ()
  81. "Initialize `nxml-mode' to take advantage of `rng-validate-mode'.
  82. This is typically called from `nxml-mode-hook'.
  83. Validation will be enabled if `rng-nxml-auto-validate-flag' is non-nil."
  84. (interactive)
  85. (define-key nxml-mode-map "\C-c\C-v" 'rng-validate-mode)
  86. (define-key nxml-mode-map "\C-c\C-s\C-w" 'rng-what-schema)
  87. (define-key nxml-mode-map "\C-c\C-s\C-a" 'rng-auto-set-schema-and-validate)
  88. (define-key nxml-mode-map "\C-c\C-s\C-f" 'rng-set-schema-file-and-validate)
  89. (define-key nxml-mode-map "\C-c\C-s\C-l" 'rng-save-schema-location)
  90. (define-key nxml-mode-map "\C-c\C-s\C-t" 'rng-set-document-type-and-validate)
  91. (define-key nxml-mode-map "\C-c\C-n" 'rng-next-error)
  92. (easy-menu-define rng-nxml-menu nxml-mode-map
  93. "Menu for nxml-mode used with rng-validate-mode."
  94. rng-nxml-easy-menu)
  95. (add-to-list 'mode-line-process
  96. '(rng-validate-mode (:eval (rng-compute-mode-line-string)))
  97. 'append)
  98. (cond (rng-nxml-auto-validate-flag
  99. (rng-validate-mode 1)
  100. (add-hook 'completion-at-point-functions #'rng-completion-at-point nil t)
  101. (add-hook 'nxml-in-mixed-content-hook #'rng-in-mixed-content-p nil t))
  102. (t
  103. (rng-validate-mode 0)
  104. (remove-hook 'completion-at-point-functions #'rng-completion-at-point t)
  105. (remove-hook 'nxml-in-mixed-content-hook #'rng-in-mixed-content-p t))))
  106. (defun rng-completion-at-point ()
  107. "Return completion data for the string before point using the current schema."
  108. (and rng-validate-mode
  109. (let ((lt-pos (save-excursion (search-backward "<" nil t)))
  110. xmltok-dtd)
  111. (and lt-pos
  112. (= (rng-set-state-after lt-pos) lt-pos)
  113. (or (rng-complete-tag lt-pos)
  114. (rng-complete-end-tag lt-pos)
  115. (rng-complete-attribute-name lt-pos)
  116. (rng-complete-attribute-value lt-pos))))))
  117. (defconst rng-in-start-tag-name-regex
  118. (replace-regexp-in-string
  119. "w"
  120. xmltok-ncname-regexp
  121. "<\\(?:w\\(?::w?\\)?\\)?\\="
  122. t
  123. t))
  124. (defun rng-complete-tag (lt-pos)
  125. (let ((extra-strings
  126. (when (and (= lt-pos (1- (point)))
  127. rng-complete-end-tags-after-<
  128. rng-open-elements
  129. (not (eq (car rng-open-elements) t))
  130. (or rng-collecting-text
  131. (rng-match-save
  132. (rng-match-end-tag))))
  133. (list (concat "/"
  134. (if (caar rng-open-elements)
  135. (concat (caar rng-open-elements)
  136. ":"
  137. (cdar rng-open-elements))
  138. (cdar rng-open-elements)))))))
  139. (when (save-excursion
  140. (re-search-backward rng-in-start-tag-name-regex
  141. lt-pos
  142. t))
  143. (and rng-collecting-text (rng-flush-text))
  144. (let ((target-names (rng-match-possible-start-tag-names)))
  145. `(,(1+ lt-pos)
  146. ,(save-excursion (skip-chars-forward "[[:alnum:]_.-:]") (point))
  147. ,(apply-partially #'rng-complete-qname-function
  148. target-names nil extra-strings)
  149. :exit-function
  150. ,(lambda (completion status)
  151. (cond
  152. ((not (eq status 'finished)) nil)
  153. ((rng-qname-p completion)
  154. (let ((name (rng-expand-qname completion
  155. t
  156. #'rng-start-tag-expand-recover)))
  157. (when (and name
  158. (rng-match-start-tag-open name)
  159. (or (not (rng-match-start-tag-close))
  160. ;; need a namespace decl on the root element
  161. (and (car name)
  162. (not rng-open-elements))))
  163. ;; attributes are required
  164. (insert " "))))
  165. ((member completion extra-strings)
  166. (insert ">")))))))))
  167. (defconst rng-in-end-tag-name-regex
  168. (replace-regexp-in-string
  169. "w"
  170. xmltok-ncname-regexp
  171. "</\\(?:w\\(?::w?\\)?\\)?\\="
  172. t
  173. t))
  174. (defun rng-complete-end-tag (lt-pos)
  175. (when (save-excursion
  176. (re-search-backward rng-in-end-tag-name-regex
  177. lt-pos
  178. t))
  179. (cond ((or (not rng-open-elements)
  180. (eq (car rng-open-elements) t))
  181. (message "No matching start-tag")
  182. (ding))
  183. (t
  184. (let ((start-tag-name
  185. (if (caar rng-open-elements)
  186. (concat (caar rng-open-elements)
  187. ":"
  188. (cdar rng-open-elements))
  189. (cdar rng-open-elements))))
  190. `(,(+ (match-beginning 0) 2)
  191. ,(save-excursion (skip-chars-forward "[[:alnum:]_.-:]") (point))
  192. ,(list start-tag-name) ;Sole completion candidate.
  193. :exit-function
  194. ,(lambda (_completion status)
  195. (when (eq status 'finished)
  196. (unless (eq (char-after) ?>) (insert ">"))
  197. (when (not (or rng-collecting-text
  198. (rng-match-end-tag)))
  199. (message "Element \"%s\" is incomplete"
  200. start-tag-name))))))))))
  201. (defconst rng-in-attribute-regex
  202. (replace-regexp-in-string
  203. "w"
  204. xmltok-ncname-regexp
  205. "<w\\(?::w\\)?\
  206. \\(?:[ \t\r\n]+w\\(?::w\\)?[ \t\r\n]*=\
  207. [ \t\r\n]*\\(?:\"[^\"]*\"\\|'[^']*'\\)\\)*\
  208. [ \t\r\n]+\\(\\(?:w\\(?::w?\\)?\\)?\\)\\="
  209. t
  210. t))
  211. (defvar rng-undeclared-prefixes nil)
  212. (defun rng-complete-attribute-name (lt-pos)
  213. (when (save-excursion
  214. (re-search-backward rng-in-attribute-regex lt-pos t))
  215. (let ((attribute-start (match-beginning 1))
  216. rng-undeclared-prefixes)
  217. (and (rng-adjust-state-for-attribute lt-pos
  218. attribute-start)
  219. (let ((target-names
  220. (rng-match-possible-attribute-names))
  221. (extra-strings
  222. (mapcar (lambda (prefix)
  223. (if prefix
  224. (concat "xmlns:" prefix)
  225. "xmlns"))
  226. rng-undeclared-prefixes)))
  227. `(,attribute-start
  228. ,(save-excursion (skip-chars-forward "[[:alnum:]_.-:]") (point))
  229. ,(apply-partially #'rng-complete-qname-function
  230. target-names t extra-strings)
  231. :exit-function
  232. ,(lambda (_completion status)
  233. (when (and (eq status 'finished)
  234. (not (looking-at "=")))
  235. (insert "=\"\"")
  236. (forward-char -1)))))))))
  237. (defconst rng-in-attribute-value-regex
  238. (replace-regexp-in-string
  239. "w"
  240. xmltok-ncname-regexp
  241. "<w\\(?::w\\)?\
  242. \\(?:[ \t\r\n]+w\\(?::w\\)?[ \t\r\n]*=\
  243. [ \t\r\n]*\\(?:\"[^\"]*\"\\|'[^']*'\\)\\)*\
  244. [ \t\r\n]+\\(w\\(:w\\)?\\)[ \t\r\n]*=[ \t\r\n]*\
  245. \\(\"[^\"]*\\|'[^']*\\)\\="
  246. t
  247. t))
  248. (defun rng-complete-attribute-value (lt-pos)
  249. (when (save-excursion
  250. (re-search-backward rng-in-attribute-value-regex lt-pos t))
  251. (let* ((name-start (match-beginning 1))
  252. (name-end (match-end 1))
  253. (colon (match-beginning 2))
  254. (value-start (1+ (match-beginning 3)))
  255. (exit-function
  256. (lambda (_completion status)
  257. (when (eq status 'finished)
  258. (let ((delim (char-before value-start)))
  259. (unless (eq (char-after) delim) (insert delim)))))))
  260. (and (rng-adjust-state-for-attribute lt-pos
  261. name-start)
  262. (if (string= (buffer-substring-no-properties name-start
  263. (or colon name-end))
  264. "xmlns")
  265. `(,value-start ,(point)
  266. ,(rng-strings-to-completion-table
  267. (rng-possible-namespace-uris
  268. (and colon
  269. (buffer-substring-no-properties (1+ colon) name-end))))
  270. :exit-function ,exit-function)
  271. (rng-adjust-state-for-attribute-value name-start
  272. colon
  273. name-end)
  274. `(,value-start ,(point)
  275. ,(rng-strings-to-completion-table
  276. (rng-match-possible-value-strings))
  277. :exit-function ,exit-function))))))
  278. (defun rng-possible-namespace-uris (prefix)
  279. (let ((ns (if prefix (nxml-ns-get-prefix prefix)
  280. (nxml-ns-get-default))))
  281. (if (and ns (memq prefix (nxml-ns-changed-prefixes)))
  282. (list (nxml-namespace-name ns))
  283. (mapcar #'nxml-namespace-name
  284. (delq nxml-xml-namespace-uri
  285. (rng-match-possible-namespace-uris))))))
  286. (defconst rng-qname-regexp
  287. (concat "\\`"
  288. xmltok-ncname-regexp
  289. "\\(?:" ":" xmltok-ncname-regexp "\\)" "?" "\\'"))
  290. (defun rng-qname-p (string)
  291. (and (string-match rng-qname-regexp string) t))
  292. (defun rng-expand-qname (qname &optional defaultp recover-fun)
  293. (setq qname (rng-split-qname qname))
  294. (let ((prefix (car qname)))
  295. (if prefix
  296. (let ((ns (nxml-ns-get-prefix qname)))
  297. (cond (ns (cons ns (cdr qname)))
  298. (recover-fun (funcall recover-fun prefix (cdr qname)))))
  299. (cons (and defaultp (nxml-ns-get-default)) (cdr qname)))))
  300. (defun rng-start-tag-expand-recover (_prefix local-name)
  301. (let ((ns (rng-match-infer-start-tag-namespace local-name)))
  302. (and ns
  303. (cons ns local-name))))
  304. (defun rng-split-qname (qname)
  305. (if (string-match ":" qname)
  306. (cons (substring qname 0 (match-beginning 0))
  307. (substring qname (match-end 0)))
  308. (cons nil qname)))
  309. (defun rng-in-mixed-content-p ()
  310. "Return non-nil if point is in mixed content.
  311. Return nil only if point is definitely not in mixed content.
  312. If unsure, return non-nil."
  313. (if (eq rng-current-schema rng-any-element)
  314. t
  315. (rng-set-state-after)
  316. (rng-match-mixed-text)))
  317. (defun rng-set-state-after (&optional pos)
  318. "Set the state for after parsing the first token with endpoint >= POS.
  319. This does not change the xmltok state or point. However, it does
  320. set `xmltok-dtd'. Returns the position of the end of the token."
  321. (unless pos (setq pos (point)))
  322. (when (< rng-validate-up-to-date-end pos)
  323. (message "Parsing...")
  324. (while (and (rng-do-some-validation)
  325. (< rng-validate-up-to-date-end pos))
  326. ;; Display percentage validated.
  327. (force-mode-line-update)
  328. (sit-for 0))
  329. (message "Parsing...done"))
  330. (save-excursion
  331. (save-restriction
  332. (widen)
  333. (nxml-with-invisible-motion
  334. (if (= pos (point-min))
  335. (rng-set-initial-state)
  336. (let ((state (get-text-property (1- pos) 'rng-state)))
  337. (cond (state
  338. (rng-restore-state state)
  339. (goto-char pos))
  340. (t
  341. (let ((start (previous-single-property-change pos
  342. 'rng-state)))
  343. (cond (start
  344. (rng-restore-state (get-text-property (1- start)
  345. 'rng-state))
  346. (goto-char start))
  347. (t (rng-set-initial-state))))))))
  348. (xmltok-save
  349. (if (= (point) 1)
  350. (xmltok-forward-prolog)
  351. (setq xmltok-dtd rng-dtd))
  352. (cond ((and (< pos (point))
  353. ;; This handles the case where the prolog ends
  354. ;; with a < without any following name-start
  355. ;; character. This will be treated by the parser
  356. ;; as part of the prolog, but we want to treat
  357. ;; it as the start of the instance.
  358. (eq (char-after pos) ?<)
  359. (<= (point)
  360. (save-excursion
  361. (goto-char (1+ pos))
  362. (skip-chars-forward " \t\r\n")
  363. (point))))
  364. pos)
  365. ((< (point) pos)
  366. (let ((rng-dt-namespace-context-getter
  367. '(nxml-ns-get-context))
  368. (rng-parsing-for-state t))
  369. (rng-forward pos))
  370. (point))
  371. (t pos)))))))
  372. (defun rng-adjust-state-for-attribute (lt-pos start)
  373. (xmltok-save
  374. (save-excursion
  375. (goto-char lt-pos)
  376. (when (memq (xmltok-forward)
  377. '(start-tag
  378. partial-start-tag
  379. empty-element
  380. partial-empty-element))
  381. (when (< start (point))
  382. (setq xmltok-namespace-attributes
  383. (rng-prune-attribute-at start
  384. xmltok-namespace-attributes))
  385. (setq xmltok-attributes
  386. (rng-prune-attribute-at start
  387. xmltok-attributes)))
  388. (let ((rng-parsing-for-state t)
  389. (rng-dt-namespace-context-getter '(nxml-ns-get-context)))
  390. (rng-process-start-tag 'stop)
  391. (rng-find-undeclared-prefixes)
  392. t)))))
  393. (defun rng-find-undeclared-prefixes ()
  394. ;; Start with the newly effective namespace declarations.
  395. ;; (Includes declarations added during recovery.)
  396. (setq rng-undeclared-prefixes (nxml-ns-changed-prefixes))
  397. (let ((iter xmltok-attributes)
  398. (ns-state (nxml-ns-state))
  399. att)
  400. ;; Add namespace prefixes used in this tag,
  401. ;; but not declared in the parent.
  402. (nxml-ns-pop-state)
  403. (while iter
  404. (setq att (car iter))
  405. (let ((prefix (xmltok-attribute-prefix att)))
  406. (when (and prefix
  407. (not (member prefix rng-undeclared-prefixes))
  408. (not (nxml-ns-get-prefix prefix)))
  409. (setq rng-undeclared-prefixes
  410. (cons prefix rng-undeclared-prefixes))))
  411. (setq iter (cdr iter)))
  412. (nxml-ns-set-state ns-state)
  413. ;; Remove namespace prefixes explicitly declared.
  414. (setq iter xmltok-namespace-attributes)
  415. (while iter
  416. (setq att (car iter))
  417. (setq rng-undeclared-prefixes
  418. (delete (and (xmltok-attribute-prefix att)
  419. (xmltok-attribute-local-name att))
  420. rng-undeclared-prefixes))
  421. (setq iter (cdr iter)))))
  422. (defun rng-prune-attribute-at (start atts)
  423. (when atts
  424. (let ((cur atts))
  425. (while (if (eq (xmltok-attribute-name-start (car cur)) start)
  426. (progn
  427. (setq atts (delq (car cur) atts))
  428. nil)
  429. (setq cur (cdr cur)))))
  430. atts))
  431. (defun rng-adjust-state-for-attribute-value (name-start
  432. colon
  433. name-end)
  434. (let* ((prefix (if colon
  435. (buffer-substring-no-properties name-start colon)
  436. nil))
  437. (local-name (buffer-substring-no-properties (if colon
  438. (1+ colon)
  439. name-start)
  440. name-end))
  441. (ns (and prefix (nxml-ns-get-prefix prefix))))
  442. (and (or (not prefix) ns)
  443. (rng-match-attribute-name (cons ns local-name)))))
  444. (defun rng-complete-qname-function (candidates attributes-flag extra-strings
  445. string predicate flag)
  446. (complete-with-action flag
  447. (rng-generate-qname-list
  448. string candidates attributes-flag extra-strings)
  449. string predicate))
  450. (defun rng-generate-qname-list (&optional string candidates attribute-flag extra-strings)
  451. (let ((forced-prefix (and string
  452. (string-match ":" string)
  453. (> (match-beginning 0) 0)
  454. (substring string
  455. 0
  456. (match-beginning 0))))
  457. (namespaces (mapcar #'car candidates))
  458. ns-prefixes-alist ns-prefixes iter ns prefer)
  459. (while namespaces
  460. (setq ns (car namespaces))
  461. (when ns
  462. (setq ns-prefixes-alist
  463. (cons (cons ns (nxml-ns-prefixes-for
  464. ns
  465. attribute-flag))
  466. ns-prefixes-alist)))
  467. (setq namespaces (delq ns (cdr namespaces))))
  468. (setq iter ns-prefixes-alist)
  469. (while iter
  470. (setq ns-prefixes (car iter))
  471. (setq ns (car ns-prefixes))
  472. (when (null (cdr ns-prefixes))
  473. ;; No declared prefix for the namespace
  474. (if forced-prefix
  475. ;; If namespace non-nil and prefix undeclared,
  476. ;; use forced prefix.
  477. (when (and ns
  478. (not (nxml-ns-get-prefix forced-prefix)))
  479. (setcdr ns-prefixes (list forced-prefix)))
  480. (setq prefer (rng-get-preferred-unused-prefix ns))
  481. (when prefer
  482. (setcdr ns-prefixes (list prefer)))
  483. ;; Unless it's an attribute with a non-nil namespace,
  484. ;; allow no prefix for this namespace.
  485. (unless attribute-flag
  486. (setcdr ns-prefixes (cons nil (cdr ns-prefixes))))))
  487. (setq iter (cdr iter)))
  488. (rng-uniquify-equal
  489. (sort (apply #'append
  490. (cons extra-strings
  491. (mapcar (lambda (name)
  492. (if (car name)
  493. (mapcar (lambda (prefix)
  494. (if prefix
  495. (concat prefix
  496. ":"
  497. (cdr name))
  498. (cdr name)))
  499. (cdr (assoc (car name)
  500. ns-prefixes-alist)))
  501. (list (cdr name))))
  502. candidates)))
  503. 'string<))))
  504. (defun rng-get-preferred-unused-prefix (ns)
  505. (let ((ns-prefix (assoc (symbol-name ns) rng-preferred-prefix-alist))
  506. iter prefix)
  507. (when ns-prefix
  508. (setq prefix (cdr ns-prefix))
  509. (when (nxml-ns-get-prefix prefix)
  510. ;; try to find an unused prefix
  511. (setq iter (memq ns-prefix rng-preferred-prefix-alist))
  512. (while (and iter
  513. (setq ns-prefix (assoc ns iter)))
  514. (if (nxml-ns-get-prefix (cdr ns-prefix))
  515. (setq iter (memq ns-prefix iter))
  516. (setq prefix (cdr ns-prefix))
  517. nil))))
  518. prefix))
  519. (defun rng-strings-to-completion-table (strings)
  520. (mapcar #'rng-escape-string strings))
  521. (provide 'rng-nxml)
  522. ;;; rng-nxml.el ends here