Introduction
The following is intended to provide instructions for setting up an (X)HTML editing environment for Emacs suitable for Web Developers. It uses psgml-mode for SGML/XML editing in conjunction with the DTDs for (X)HTML from the W3C, and uses mmm-mode to define sub-modes for Javascript (using javascript-generic-mode), CSS (using css-mode), and PHP (using php-mode). It also provides support from within Emacs for HTML Tidy.
Note that psgml-mode, and this setup, rely upon certain other packages being installed on your system. In my case, on Red Hat Linux 9, these are openjade, sgml-common, and xml-common. These packages, or similar, are easily available on modern Linux distributions. It is not within the scope of this document to explain how to install these on other OSs such as Windows, Solaris, or Mac OS. Neither is it within the scope of this document to explain how to install Emacs, or how to use it. It is assumed throughout that you have an installed, recent, version of Emacs, and that you are familiar with its usage.
This document was largely inspired by the excellent advice on HtmlModeDeluxe given at EmacsWiki. Comments are very welcome.
Downloading and Installing the Emacs Lisp Packages
You will need the following packages:
- psgml
- mmm
- generic-x
- php-mode
- css-mode
- xxml
- tidy
In my case, both generic-x and php-mode were distributed with Emacs. However, given where the Red Hat Linux 9 Emacs package installs php-mode I am guessing that it is not part of a standard Emacs distribution. If you do not have it, you can download it from here. css-mode can be downloaded from here. xxml can be downloaded from here. tidy can be downloaded from here. All of these files should be byte-compiled and installed somewhere Emacs knows where to find them, such as /usr/share/emacs/site-lisp or /usr/local/share/emacs/site-lisp. If you would prefer to install them in a directory of your own choosing, perhaps in your home directory, then remember to add something like the following to your .emacs:
(add-to-list 'load-path "path/to/your/lisp/files")
psgml is available as an RPM package with Red Hat Linux 9, although it is a slightly out-of-date version (1.2.3); I would recommend downloading the 1.2.5 package from Rawhide. These are no "noarch" packages, and so may well work on other RPM-based distributions, but if in doubt, or if you are not using Linux or an RPM-based Linux distribution then you can download psgml from sourceforge in tarball format. Installation is fairly straightforward: unpack the tarball and follow the installation instructions in the README.psgml and INSTALL files.
Per the instructions on EmacsWiki I use this version of mmm-mode. Again, just unpack the tarball, byte-compile the files, and install them somewhere Emacs can find them.
Downloading and Installing the DTDs
I have made a tarball containing all the DTDs for HTML 4.01, XHTML 1.0 and XHTML 1.1, together with a catalog, and an ecatalog, available here. I install this in ~/lib/DTD. If you choose to install it elsewhere then you will need to change many of the pathnames in the excerpts from my .emacs below. Assuming that you will simply install them as I have, download the tarball to your home directory and issue the following commands:
cd ~ ; tar -xjvf dtds.tar.bz2
You'll need to set the following in your .bash_profile, or however else you set environment variables on your machine.
SGML_CATALOG_FILES=$HOME/lib/DTD/catalog:./catalog:/etc/sgml/catalog
export SGML_CATALOG_FILES
Alternatively this can be set from within emacs by customizing the sgml-catalog-files variable.
Naturally you'll have to adapt the last part so that it picks up wherever your system SGML catalog is.
Configuring Emacs
Next, Emacs needs to be configured to use everything you've just downloaded. You have to add a bunch of stuff to your .emacs. There are some hard-coded locations in here which are obviously system-dependent. If you're running RHL9 then you won't need to change a thing. If you're not, then you'll need to change some of the paths to point to the right files.
Below are the relevant parts of my .emacs broken up into sections for your convenience.
Set up psgml
;; ---------------------------------------------------------------------------
;; XML/SGML - PSGML
;;
;; First, we'll just set up PSGML. Then we'll create a derived mode for
;; (X)HTML using mmm-mode for fancy (X)HTML/CSS/Javascript/PHP coding
(autoload 'sgml-mode "psgml" "Major mode to edit SGML files." t )
(autoload 'xml-mode "psgml" "Major mode to edit XML files." t)
(setq auto-mode-alist
(append
(list
'("\\.sgm$" . sgml-mode)
'("\\.sgml$" . sgml-mode)
'("\\.xml$" . xml-mode)
)
auto-mode-alist))
;; Enable editing help with mouse-3 in all sgml files
(defun go-bind-markup-menu-to-mouse3 ()
(define-key sgml-mode-map [(down-mouse-3)] 'sgml-tags-menu))
(add-hook 'sgml-mode-hook 'go-bind-markup-menu-to-mouse3)
Syntax highlighting
;; xxml.el for better syntax highlighting (although we won't use xxml.el's
;; html-mode, but will define our own).
; (autoload 'html-mode "xxml" "Major mode to edit HTML files." t)
(autoload 'xxml-mode-routine "xxml")
(add-hook 'sgml-mode-hook 'xxml-mode-routine)
(add-hook 'xml-mode-hook 'xxml-mode-routine)
Note that using xxml for syntax highlighting obviates the need to set up psgml's faces. The following is therefore not required (but is included here for reference because so many other guides to setting up psgml make reference to similar configurations):
;; create faces to assign to markup categories
(make-face 'sgml-comment-face)
(make-face 'sgml-start-tag-face)
(make-face 'sgml-end-tag-face)
(make-face 'sgml-entity-face)
(make-face 'sgml-doctype-face) ; DOCTYPE data
(make-face 'sgml-ignored-face) ; data ignored by PSGML
(make-face 'sgml-ms-start-face) ; marked sections start
(make-face 'sgml-ms-end-face) ; end of marked section
(make-face 'sgml-pi-face) ; processing instructions
(make-face 'sgml-sgml-face) ; the SGML declaration
(make-face 'sgml-shortref-face) ; short references
;; assign faces to markup categories
(setq sgml-markup-faces '
(
(comment . sgml-comment-face)
(start-tag . sgml-start-tag-face)
(end-tag . sgml-end-tag-face)
(entity . sgml-entity-face)
(doctype . sgml-doctype-face)
(ignored . sgml-ignored-face)
(ms-start . sgml-ms-start-face)
(ms-end . sgml-ms-end-face)
(pi . sgml-pi-face)
(sgml . sgml-sgml-face)
(shortref . sgml-shortref-face)
))
;; tell PSGML to pay attention to face settings
(setq sgml-set-face t)
Add support for the W3C (X)HTML DTDs
;; Set up "DTD->Insert DTD" menu.
(setq sgml-custom-dtd
'(
( "HTML 4.01 Strict"
"<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01//EN\"\n \"http://www.w3.org/TR/html4/strict.dtd\">"
sgml-declaration "~/lib/DTD/html401/HTML4.decl"
sgml-default-dtd-file "~/lib/DTD/html401/strict.ced"
mode sgml-html )
( "HTML 4.01 Transitional"
"<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\n \"http://www.w3.org/TR/html4/loose.dtd\">"
sgml-declaration "~/lib/DTD/html401/HTML4.decl"
sgml-default-dtd-file "~/lib/DTD/html401/loose.ced"
mode sgml-html )
( "HTML 4.01 Frameset"
"<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Frameset//EN\"\n \"http://www.w3.org/TR/html4/frameset.dtd\">"
sgml-declaration "~/lib/DTD/html401/HTML4.decl"
sgml-default-dtd-file "~/lib/DTD/html401/frameset.ced"
mode sgml-html )
( "XHTML 1.0 Strict"
"<?xml version=\"1.0\"?>\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"\n \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">"
sgml-default-dtd-file "~/lib/DTD/xhtml1/xhtml1-strict.ced"
mode xml-html )
( "XHTML 1.0 Transitional"
"<?xml version=\"1.0\"?>\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">"
sgml-default-dtd-file "~/lib/DTD/xhtml1/xhtml1-transitional.ced"
mode xml-html )
( "XHTML 1.0 Frameset"
"<?xml version=\"1.0\"?>\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Frameset//EN\"\n \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd\">"
sgml-default-dtd-file "~/lib/DTD/xhtml1/xhtml1-frameset.ced"
mode xml-html )
( "XHTML 1.1"
"<?xml version=\"1.0\"?>\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\"\n \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">"
sgml-default-dtd-file "~/lib/DTD/xhtml11/xhtml11.ced"
mode xml-html )
)
)
;; ecat support
(setq sgml-ecat-files
(list
(expand-file-name "~/lib/DTD/ecatalog")
)
)
Use mmm-mode to define sub-modes for CSS, Javascript and PHP
;; ---------------------------------------------------------------------------
;; (X)HTML - PSGML with MMM Mode for CSS, Javascript and PHP support
(define-derived-mode xml-html-mode xml-mode "XHTML"
"This version of html mode is just a wrapper around xml mode."
(make-local-variable 'sgml-declaration)
(make-local-variable 'sgml-default-doctype-name)
(setq
sgml-default-doctype-name "html"
sgml-declaration "/usr/share/sgml/xml.dcl"
sgml-always-quote-attributes t
sgml-indent-step 2
sgml-indent-data t
sgml-minimize-attributes nil
sgml-omittag nil
sgml-shorttag nil
)
)
(define-derived-mode sgml-html-mode sgml-mode "HTML"
"This version of html mode is just a wrapper around sgml mode."
(make-local-variable 'sgml-declaration)
(make-local-variable 'sgml-default-doctype-name)
(setq
sgml-default-doctype-name "html"
sgml-declaration "~/lib/DTD/html401/HTML4.decl"
sgml-always-quote-attributes t
sgml-indent-step 2
sgml-indent-data t
sgml-minimize-attributes nil
sgml-omittag nil
sgml-shorttag nil
)
)
;; PHP-Mode
(require 'php-mode)
(add-hook 'php-mode-user-hook 'turn-on-font-lock)
;; CSS-Mode
(autoload 'css-mode "css-mode")
(add-to-list 'auto-mode-alist '("\\.css\\'" . css-mode))
(setq cssm-indent-function #'cssm-c-style-indenter)
(setq cssm-indent-level '2)
;; javascript-generic-mode
(require 'generic-x)
;; MMM-Mode
(require 'mmm-auto)
(setq mmm-global-mode 'maybe)
;; Set up an mmm group for fancy html editing
(mmm-add-group
'fancy-html
'(
(html-php-embedded
:submode php-mode
:face mmm-output-submode-face
:front "<[?]php"
:back "[?]>")
(html-css-embedded
:submode css-mode
:face mmm-declaration-submode-face
:front "<style\[^>\]*>"
:back "</style>")
(html-css-attribute
:submode css-mode
:face mmm-declaration-submode-face
:front "\\bstyle=\\s-*\""
:back "\"")
(html-javascript-embedded
:submode javascript-generic-mode
:face mmm-code-submode-face
:front "<script\[^>\]*>"
:back "</script>")
(html-javascript-attribute
:submode javascript-generic-mode
:face mmm-code-submode-face
:front "\\bon\\w+=\\s-*\""
:back "\"")
)
)
;; What files to invoke the new html-mode for?
;; HTML and XHTML tend to have the same file extensions. However, XHTML
;; hopefully has either one or both of <?xml or W3C//DTD XHTML near the
;; beginning. So we default to sgml-html-mode and then add a hook to
;; sgml-html-mode to check for either of those strings and switch to
;; xml-html-mode if it finds them.
(add-to-list 'auto-mode-alist '("\\.inc\\'" . sgml-html-mode))
(add-to-list 'auto-mode-alist '("\\.php[34]?\\'" . sgml-html-mode))
(add-to-list 'auto-mode-alist '("\\.[sj]?html?\\'" . sgml-html-mode))
(add-hook 'sgml-html-mode-hook 'guess-xhtml-hook t)
(defun guess-xhtml-hook ()
"Guess whether the current buffer is XHTML."
(when
(save-excursion
(search-forward-regexp "<[?]xml\\|//W3C//DTD XHTML" 80 t))
(xml-html-mode)
))
;; Make smgl-html-mode and xml-html-mode use fancy-html
(add-to-list 'mmm-mode-ext-classes-alist '(sgml-html-mode nil fancy-html))
(add-to-list 'mmm-mode-ext-classes-alist '(xml-html-mode nil fancy-html))
Add an interface to Tidy
;; Use tidy.el to provide support for tidy
(autoload 'tidy-buffer "tidy" "Run Tidy HTML parser on current buffer" t)
(autoload 'tidy-parse-config-file "tidy" "Parse the `tidy-config-file'" t)
(autoload 'tidy-save-settings "tidy" "Save settings to `tidy-config-file'" t)
(autoload 'tidy-build-menu "tidy" "Install an options menu for HTML Tidy." t)
(add-hook 'sgml-html-mode-hook #'(lambda () (tidy-build-menu sgml-html-mode-map)))
(add-hook 'xml-html-mode-hook #'(lambda () (tidy-build-menu xml-html-mode-map)))
Customizations
I tend to use Emacs' customize for setting the values of various variables, rather than setting everything by hand in my .emacs. Here are the relevant customizations:
'(sgml-always-quote-attributes t t)
'(sgml-angle-distance 4000 t)
'(sgml-auto-activate-dtd t t)
'(sgml-auto-insert-required-elements t t)
'(sgml-balanced-tag-edit t t)
'(sgml-buggy-subst-char-in-region nil t)
'(sgml-custom-markup nil t)
'(sgml-declaration "~/lib/DTD/html401/HTML4.decl" t)
'(sgml-default-doctype-name nil t)
'(sgml-default-dtd-file nil t)
'(sgml-doctype nil t)
'(sgml-exposed-tags nil t)
'(sgml-general-insert-case (quote lower) t)
'(sgml-ignore-undefined-elements nil t)
'(sgml-indent-data t t)
'(sgml-indent-step 2 t)
'(sgml-insert-defaulted-attributes nil t)
'(sgml-insert-end-tag-on-new-line nil t)
'(sgml-insert-missing-element-comment nil t)
'(sgml-leave-point-after-insert nil t)
'(sgml-live-element-indicator t t)
'(sgml-local-catalogs nil t)
'(sgml-local-ecat-files nil t)
'(sgml-max-menu-size 48 t)
'(sgml-minimize-attributes nil t)
'(sgml-namecase-general t t)
'(sgml-normalize-trims t t)
'(sgml-offer-save t t)
'(sgml-omittag nil t)
'(sgml-omittag-transparent t t)
'(sgml-parent-document nil t)
'(sgml-range-indicator-max-length 9 t)
'(sgml-recompile-out-of-date-cdtd (quote ask) t)
'(sgml-set-face t t)
'(sgml-shorttag nil t)
'(sgml-slash-distance 1000 t)
'(sgml-system-identifiers-are-preferred nil t)
'(sgml-tag-region-if-active nil t)
'(sgml-trace-entity-lookup nil t)
'(sgml-validate-command "nsgmls -s %s %s")
'(sgml-warn-about-undefined-elements t t)
'(sgml-warn-about-undefined-entities t t)
'(sgml-xml-declaration "/usr/share/sgml/xml.dcl" t)
'(sgml-xml-validate-command "nsgmls -wxml -s %s %s" t)
If you see a way of improving what I've got then please let me know.
TODO
The above provides a complete, working editing environment for (X)HTML, Javascript, CSS, and PHP. I use it daily, and am extremely happy with it. Nevertheless, as with all things, there is always room for improvement.
The future of psgml and mmm-mode
One possible cause for concern is that neither psgml nor mmm-mode are in active development. In the case of psgml I believe that concern to be groundless; in the case of mmm-mode there may be some grounds for concern.
psgml
The fact is that psgml is not in active development because it is pretty much feature complete. It is maintained, and bugs are fixed when discovered. The features that are sometimes requested are either already provided by other packages (e.g. better syntax highlighting is provided by xxml.el), or are beyond the scope of psgml. For example, it is wholly impractical to suggest adding support for XML namespaces and schemas. psgml is specifically designed to work with a DTD; since DTDs do not support namespaces, and schemas are an alternative to DTDs, there is simply no practical way that such support could be provided. Given that nXML-mode (see below) is now available it also seems pointless.
mmm-mode
That mmm-mode is not in active development is more of a concern. The Emacs CVS TODO states, under "Other features we would like":
Implement a clean way to use different major modes for different parts of a buffer.
Whether the existing mmm-mode codebase would count as sufficiently "clean" is not something I am qualified to comment upon. We need a talented lisp programmer to step up and tackle this issue. Sadly, that task is several orders of magnitde beyond my meagre lisp skills.
Integration with nXML-mode
James Clark's nXML-mode (read about it here) does for XML editing with schemas and namespaces what psgml does for SGML/XML editing with DTDs. It is still in alpha status at the time of writing, and in my personal opinion its user interface at present leaves much to be desired. Nevertheless, as it matures, and as working with schemas, namespaces, and hybrid documents becomes much more commonplace, we will want to integrate nXML-mode into our environment, preferably allowing us to easily switch between it and psgml.
Better support for Javascript and CSS
At the moment support for Javascript and CSS is pretty bare-bones. Having said that, I have no clear idea of what more I would like from a Javascript mode. css-mode on the other hand absolutely cries out for a menu. There is a fixed number of CSS properties, and in the case of some of those properties a fixed number of possible values. Inserting those via a menu would be an enormous convenience. I intend to work on this myself.
Support for all W3C DTDs and schemas
Why oh why don't the W3C make all of their DTDs (and schemas) available for download in one package complete with a catalog? At the moment, maintaining a catalog is a royal pain in the arse. We should try and bug them to do something about this.
