seml-mode.el provides a major mode for editing SEML (S-Expression Markup Language) files. SEML lets you write HTML using familiar Lisp S-expressions instead of angle brackets.
The two files below represent the same structure. SEML is concise and intuitive for Lisp users:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>sample page</title>
<link rel="stylesheet" href="sample1.css"/>
</head>
<body>
<h1>sample</h1>
<p>
text sample
</p>
</body>
</html>(html ((lang . "en"))
(head nil
(meta ((charset . "utf-8")))
(title nil "sample page")
(link ((rel . "stylesheet") (href . "sample1.css"))))
(body nil
(h1 nil "sample")
(p nil "text sample")))
Since SEML supports full Elisp evaluation within templates, you can open files, access Emacs internals, call external APIs, and generate dynamic content - similar to what PHP offers for HTML templating.
The basic syntax is straightforward:
(TAG ATTRS VALUE...)TAGis a symbol representing an HTML tag nameATTRSis a list of attributes as dotted pairs(ATTR . VALUE)- Attributes also support Jade/Pug-style shorthand like
#id.class1.class2 - The id must come first if specified, and classes are separated by
.
- Attributes also support Jade/Pug-style shorthand like
VALUEis a string or another nested SEML expression
(cort-deftest seml-mode:/simple-jade
(:string= (seml-decode-seml-from-sexp '(h1 ("#header.class1.class2") "sample"))
"<h1 id=\"header\" class=\"class1 class2\">sample</h1>"))
(cort-deftest seml-mode:/simple-jade2
(:string= (seml-decode-seml-from-sexp '(h1 ("#header.class1") "sample"))
"<h1 id=\"header\" class=\"class1\">sample</h1>"))
(cort-deftest seml-mode:/simple-jade3
(:string= (seml-decode-seml-from-sexp '(h1 ("class1") "sample"))
"<h1 class=\"class1\">sample</h1>"))Since SEML is just Elisp, you can generate content programmatically. Pass any SEML list to the decoder:
(cort-deftest seml-mode:/simple-ul
(:string= (seml-decode-seml-from-sexp
`(ul nil
,@(mapcar (lambda (x)
`(li nil ,(format "item-%s" x)))
(number-sequence 1 5))))
"<ul>
<li>item-1</li>
<li>item-2</li>
<li>item-3</li>
<li>item-4</li>
<li>item-5</li>
</ul>"))You can use any function that returns a list.
Requires Emacs 25.1 or later.
;; Using package.el
(package-install 'seml-mode)
;; Using use-package
(use-package seml-mode
:ensure t)
;; Using leaf.el
(leaf seml-mode
:ensure t)Add this package to your load-path and require it:
(add-to-list 'load-path
(locate-user-emacs-file "site-lisp/seml-mode.el"))
(require 'seml-mode)The mode automatically activates for .seml files. For custom extensions, add:
(add-to-list 'auto-mode-alist '("\\.seml\\'" . seml-mode))
(add-to-list 'interpreter-mode-alist '("seml" . seml-mode))seml-mode-hookseml-import-dirseml-live-refresh-intervalseml-live-refresh-url-variableseml-live-refresh-url-quety
seml-mode-keywords- Supported HTML5 tags:(defconst seml-mode-keywords '(html head title base link meta style script noscript body section nav article aside hgroup header footer address h1 h2 h3 h4 h5 h6 p hr pre backquote ol ul li dl dt dd figure figcaption div main a em strong small s cite q dfn addr time code var samp kbd sub sup i b mark ruby rt rpbdo span br wbr ins del img iframe embed object param video audio source canvas map area table caption colgroup col tbody thead tfoot tr td th form fieldset legend label input button select datalist optgroup option textarea keygen output progress meter details summary command menu ;; libxml-parse keywords comment top))
seml-html-single-tags- Self-closing tags:(defconst seml-html-single-tags '(base link meta img br area param hr col option input wbr))
with-seml-elisp- Evaluate Elisp without returning a value to SEML. Use,@(with-seml-elisp (sexp) (sexp) ...)when you need to execute Elisp that should not contribute to the SEML output.
(seml-encode-region-from-html pointmin pointmax)(seml-encode-string-from-html str)(seml-encode-buffer-from-html &optional buf)(seml-encode-file-from-html filepath)
(seml-decode-region-from-seml start end &optional doctype)(seml-decode-sexp-from-seml sexp &optional doctype)(seml-decode-string-from-seml str &optional doctype)(seml-decode-buffer-from-seml &optional buf doctype)(seml-decode-file-from-seml filepath &optional doctype)
(seml-replace-region-from-html)(seml-replace-region-from-seml)
(seml-impatient-mode)- Enable live browser preview as you edit.
(seml-indent-function indent-point state)(seml-to-string sexp)(seml-pp sexp &optional stream return-p)(seml-xpath xpath sexp &optional without-top)- Navigate SEML with XPath-like paths:(cort-deftest seml-test:simple-xpath (:equal (seml-xpath '(html head link) '(html ((lang . "en")) (head nil (meta ((charset . "utf-8"))) (title nil "sample page") (link ((rel . "stylesheet") (href . "sample1.css"))) (link ((rel . "stylesheet") (href . "sample2.css")))) (body nil (h1 nil "sample") (p nil "sample" "text sample")))) '((link ((rel . "stylesheet") (href . "sample1.css"))) (link ((rel . "stylesheet") (href . "sample2.css"))))))
(seml-xpath-single xpath sexp &optional without-top)- Return first matching element:(cort-deftest seml-test:/simple-xpath-single (:equal (seml-xpath-single '(html body) '(html ((lang . "en")) (head nil (meta ((charset . "utf-8"))) (title nil "sample page") (link ((rel . "stylesheet") (href . "sample1.css"))) (link ((rel . "stylesheet") (href . "sample2.css")))) (body nil (h2 nil "sample-1") (h2 nil "sample-2") (h2 nil "sample-3") (p nil "sample" "text sample")))) '(body nil (h2 nil "sample-1") (h2 nil "sample-2") (h2 nil "sample-3") (p nil "sample" "text sample"))))
(seml-xpath-without-top xpath sexp)- Return matches without the top element:(cort-deftest seml-test:/simple-xpath-without-top (:equal (seml-xpath '(html body h2) '(html ((lang . "en")) (head nil (meta ((charset . "utf-8"))) (title nil "sample page") (link ((rel . "stylesheet") (href . "sample1.css"))) (link ((rel . "stylesheet") (href . "sample2.css")))) (body nil (h2 nil "sample-1") (h2 nil "sample-2") (h2 nil "sample-3") (p nil "sample" "text sample"))) t) '(("sample-1") ("sample-2") ("sample-3"))))
(seml-xpath-single-without-top xpath sexp)(seml-htmlize majormode codestr &optional noindentp formatfn)- Generate SEML with syntax highlighting:(cort-deftest seml-mode:/simple-htmlize (:equal (seml-htmlize 'emacs-lisp-mode "(leaf real-auto-save :ensure t :custom ((real-auto-save-interval . 0.3)) :commands real-auto-save-mode :hook (find-file-hook . real-auto-save-mode))") '(pre nil " (" (span ((class . "keyword")) "leaf") " real-auto-save " (span ((class . "builtin")) ":ensure") " t " (span ((class . "builtin")) ":custom") " ((real-auto-save-interval . 0.3)) " (span ((class . "builtin")) ":commands") " real-auto-save-mode " (span ((class . "builtin")) ":hook") " (find-file-hook . real-auto-save-mode))")))
(seml-import path)(seml-expand-url path baseurl)
(seml-mode)
- leaf-browser.el - Web browser frontend for Emacs customize-mode with leaf.el
Contributions are welcome. Please feel free to submit issues and pull requests on GitHub.
AGPLv3. See LICENSE for details.
Naoya Yamashita (conao3)


