#!/bin/bash
# $Header: /var/cvsroot/gentoo-src/build-docbook-catalog/build-docbook-catalog,v 1.19 2012/03/28 19:34:46 vapier Exp $
#
# build-docbook-catalog: populate /etc/xml/docbook based in
# installed docbook-xml-dtd versions.
#
# Copyright 2004-2012 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2
# written by Aron Griffis
#

ROOTCONFDIR=/etc/xml
ROOTCATALOG=${ROOTCONFDIR}/catalog
CATALOG=${ROOTCONFDIR}/docbook
DOCBOOKDIR=/usr/share/sgml/docbook
DTDS=
LATEST_DTD=
LATEST_DATE=
VERBOSE=false
ZERO=${0##*/}

#
# usage!
#
usage() {
	cat <<-EOF
	Usage: ${ZERO} [options]

	Options:
	  -r, --root       ROOT path to work on
	  -v, --verbose    Be verbose
	  -h, --help       This!
	EOF
	[[ $# -gt 0 ]] && eerror "$*"
	exit 0
}

#
# main (called from bottom)
#
main() {
	local d v opts

	opts=$(getopt -o hr:v --long help,root:,verbose -n "${ZERO}" -- "$@") || exit 1
	eval set -- "${opts}"
	while true; do
		case $1 in
			-h|--help) usage ;;
			-r|--root) ROOT=$2 ; shift ;;
			-v|--verbose) VERBOSE=true ;;
			--) break ;;
			*) usage "options parsing failed on $1!" ;;
		esac
		shift
	done

	: ${ROOT:=/}
	[[ ${ROOT} != */ ]] && ROOT="${ROOT}/"
	[[ ${ROOT} != /* ]] && ROOT="${PWD}${ROOT}"
	if [[ ${ROOT} != "/" ]] ; then
		echo "Working on root ${ROOT}"
	fi

	if [[ ! -d ${ROOT}${ROOTCONFDIR} ]] ; then
		mkdir -p "${ROOT}${ROOTCONFDIR}" || error "could not create ${ROOTCONFDIR}"
	fi

	(
	# Lock the dir to avoid trashing other runs that might
	# be running parallel.
	flock 123 || error "unable to lock ${ROOTCONFDIR}"

	create_catalogs			# will exit on error
	for type in xsl xsl-ns xsl-saxon xsl-xalan; do
		populate_xsl ${type}
	done

	# Clean out old dtds from catalog
	verb "Cleaning out old DocBook XML versions from ${CATALOG} and ${ROOTCATALOG}"
	clean_catalog "${DOCBOOKDIR}/xml\(-simple\)*-dtd-[^/\"']*/[^/\"']*" "${CATALOG}"
	clean_catalog "${DOCBOOKDIR}/xml\(-simple\)*-dtd-[^/\"']*/[^/\"']*" "${ROOTCATALOG}"

	if set_dtds; then
		for d in ${DTDS}; do
			populate_dtd ${d}
		done
		for d in ${SIMPLE_DTDS}; do
			populate_simple_dtd ${d}
		done
		populate_entities
	fi

	) 123>"${ROOT}${ROOTCONFDIR}/.keep"

	exit 0
}

#
# verbose echo -- only echo if called with --verbose
#
verb() {
	${VERBOSE} && echo "$*"
}

#
# show an error and abort
#
error() {
	printf '%s: %b, aborting\n' "${ZERO}" "$*" 1>&2
	exit 1
}

#
# fill in the DTDS variable based on installed versions
#
set_dtds() {
	DTDS= SIMPLE_DTS=

	local d=${ROOT}${DOCBOOKDIR}
	if [[ -d ${d} ]] ; then
		pushd "${d}" >/dev/null || return 1
		DTDS=$(find xml-dtd-*/ -name docbookx.dtd)
		SIMPLE_DTDS=$(find xml-simple-dtd-*/ -name sdocbook.dtd)
		popd >/dev/null
	fi

	if [[ -z ${DTDS} ]]; then
		echo "No installed DocBook XML DTDs found"
		return 1
	else
		return 0
	fi
}

#
# multi_xmlcatalog_add <file> <opts array>
#
# the opts array is a set of three: what gets passed to --add
#
multi_xmlcatalog_add() {
	local file="${ROOT}$1"
	shift

	while [[ $# -gt 0 ]] ; do
		xmlcatalog --noout --add "$1" "$2" "file://$3" "${file}"
		shift 3
	done
}

#
# create the catalogs root and docbook specific
#
create_catalogs() {
	local adds

	if [[ ! -r ${ROOT}${ROOTCATALOG} ]] ; then
		echo "Creating XML Catalog root ${ROOTCATALOG}"
		xmlcatalog --noout --create "${ROOT}${ROOTCATALOG}"
		if [[ ! -r ${ROOT}${ROOTCATALOG} ]] ; then
			error "failed creating ${ROOTCATALOG}"
		fi
	else
		verb "Found XML Catalog root ${ROOTCATALOG}"
		# clean out existing entries
		verb "  Cleaning existing ${CATALOG} delegates from ${ROOTCATALOG}"
		clean_catalog "file://${CATALOG}" "${ROOTCATALOG}"
	fi

	if [[ ! -r ${ROOT}${CATALOG} ]] ; then
		echo "Creating DocBook XML Catalog ${CATALOG}"
		xmlcatalog --noout --create "${ROOT}${CATALOG}"
		if [[ ! -r ${ROOT}${CATALOG} ]] ; then
			error "failed creating ${CATALOG}"
		fi
	else
		verb "Found DocBook XML Catalog ${CATALOG}"
	fi

	# dtd pointers
	verb "  Populating ${ROOTCATALOG} with DTD delegates to ${CATALOG}"
	adds=(
		"delegatePublic"  "-//OASIS//ENTITIES DocBook"          "${CATALOG}"
		"delegatePublic"  "-//OASIS//ELEMENTS DocBook"          "${CATALOG}"
		"delegatePublic"  "-//OASIS//DTD DocBook"               "${CATALOG}"
		"delegateSystem"  "http://www.oasis-open.org/docbook/"  "${CATALOG}"
		"delegateURI"     "http://www.oasis-open.org/docbook/"  "${CATALOG}"
	)
	multi_xmlcatalog_add "${ROOTCATALOG}" "${adds[@]}"

	# entities pointer
	verb "  Populating ${ROOTCATALOG} with ISO entities delegate to ${CATALOG}"
	adds=(
		"delegatePublic"  "ISO 8879:1986"  "${CATALOG}"
	)
	multi_xmlcatalog_add "${ROOTCATALOG}" "${adds[@]}"
}

#
# clean_catalog
# $1 == regex to clean
# $2 == catalog
#
clean_catalog() {
	local list f regex=$1 catalog=${ROOT}$2

	list=$(egrep --only-matching "${regex}" "${catalog}" | sort -u)
	for f in ${list}; do
		xmlcatalog --noout --del "${f}" "${catalog}"
	done
}

#
# populate a specific dtd version into the docbook catalog
# $1 == ./subpath/to/docbookx.dtd
#
populate_dtd() {
	local dtd=${DOCBOOKDIR}/$1
	local docbookdir=${dtd%/*}
	local v=${docbookdir##*-}
	local adds dtd_date

	# sanity check
	if [[ ${dtd} != */xml-dtd-*/* ]]; then
		echo "Warning: I don't understand \"${dtd}\"" >&2
		return
	fi
	echo "Found DocBook XML ${v} in ${docbookdir}"

	# Populate the docbook catalog with this version
	verb "  Populating ${CATALOG} based on ${docbookdir}"
	adds=(
		"public"         "-//OASIS//ELEMENTS DocBook XML Information Pool V${v}//EN"             "${docbookdir}/dbpoolx.mod"
		"public"         "-//OASIS//DTD DocBook XML V${v}//EN"                                   "${docbookdir}/docbookx.dtd"
		"public"         "-//OASIS//ENTITIES DocBook XML Character Entities V${v}//EN"           "${docbookdir}/dbcentx.mod"
		"public"         "-//OASIS//ENTITIES DocBook XML Notations V${v}//EN"                    "${docbookdir}/dbnotnx.mod"
		"public"         "-//OASIS//ENTITIES DocBook XML Additional General Entities V${v}//EN"  "${docbookdir}/dbgenent.mod"
		"public"         "-//OASIS//ELEMENTS DocBook XML Document Hierarchy V${v}//EN"           "${docbookdir}/dbhierx.mod"
		"public"         "-//OASIS//DTD XML Exchange Table Model 19990315//EN"                   "${docbookdir}/soextblx.dtd"
		"public"         "-//OASIS//DTD DocBook XML CALS Table Model V${v}//EN"                  "${docbookdir}/calstblx.dtd"
		"rewriteSystem"  "http://www.oasis-open.org/docbook/xml/${v}"                            "${docbookdir}"
		"rewriteURI"     "http://www.oasis-open.org/docbook/xml/${v}"                            "${docbookdir}"
	)
	multi_xmlcatalog_add "${CATALOG}" "${adds[@]}"

	# grab the RCS date from docbookx.dtd for comparison purposes
	if [[ ! -f ${ROOT}${docbookdir}/ent/iso-lat1.ent ]]; then
		verb "  No entities available for ${dtd}"
		return 0
	fi
	dtd_date=$(egrep --only-matching --max-count=1 \
		'[0-9]{4}/[0-9]{2}/[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}' \
		"${ROOT}${dtd}")
	if [[ -z ${dtd_date} ]]; then
		verb "  Couldn't find RCS date in ${dtd}, ignoring entities"
		return 0
	fi
	verb "  RCS datestamp in ${dtd} is ${dtd_date}"
	dtd_date=$(date -d "${dtd_date}" +%s)
	if [[ ${dtd_date} -gt ${LATEST_DATE:-0} ]] ; then
		LATEST_DATE=${dtd_date}
		LATEST_DTD=${dtd}
	fi
}

#
# populate a specific simple dtd version into the docbook catalog
# $1 == ./subpath/to/sdocbook.dtd
#
populate_simple_dtd() {
	local dtd=${DOCBOOKDIR}/$1
	local docbookdir=${dtd%/*}
	local v=${docbookdir##*-}
	local adds dtd_date

	# sanity check
	if [[ ${dtd} != */xml-simple-dtd-*/* ]]; then
		echo "Warning: I don't understand \"${dtd}\"" >&2
		return
	fi
	echo "Found Simplified DocBook XML ${v} in ${docbookdir}"

	# Populate the docbook catalog with this version
	verb "  Populating ${CATALOG} based on ${docbookdir}"
	adds=(
		"public"         "-//OASIS//DTD Simplified DocBook XML V${v}//EN"     "${docbookdir}/sdocbook.dtd"
		"rewriteSystem"  "http://www.oasis-open.org/docbook/xml/simple/${v}"  "${docbookdir}"
		"rewriteURI"     "http://www.oasis-open.org/docbook/xml/simple/${v}"  "${docbookdir}"
	)
	multi_xmlcatalog_add "${CATALOG}" "${adds[@]}"
}

#
# populate ISO DocBook entities from the most recent DTD
#
populate_entities() {
	local isodir=${LATEST_DTD%/*}/ent i j
	local entities=() avail=()

	# sanity check
	if [[ -z ${LATEST_DTD} || ! -d ${ROOT}${isodir} ]]; then
		echo "No ISO DocBook entities available for catalog"
		return 0
	fi
	echo "Using ISO DocBook entities from ${isodir}"

	# here are the entities we know about;
	# note these must remain sorted!
	entities=(
		"iso-amsa.ent" "ISO 8879:1986//ENTITIES Added Math Symbols: Arrow Relations//EN"
		"iso-amsb.ent" "ISO 8879:1986//ENTITIES Added Math Symbols: Binary Operators//EN"
		"iso-amsc.ent" "ISO 8879:1986//ENTITIES Added Math Symbols: Delimiters//EN"
		"iso-amsn.ent" "ISO 8879:1986//ENTITIES Added Math Symbols: Negated Relations//EN"
		"iso-amso.ent" "ISO 8879:1986//ENTITIES Added Math Symbols: Ordinary//EN"
		"iso-amsr.ent" "ISO 8879:1986//ENTITIES Added Math Symbols: Relations//EN"
		"iso-box.ent" "ISO 8879:1986//ENTITIES Box and Line Drawing//EN"
		"iso-cyr1.ent" "ISO 8879:1986//ENTITIES Russian Cyrillic//EN"
		"iso-cyr2.ent" "ISO 8879:1986//ENTITIES Non-Russian Cyrillic//EN"
		"iso-dia.ent" "ISO 8879:1986//ENTITIES Diacritical Marks//EN"
		"iso-grk1.ent" "ISO 8879:1986//ENTITIES Greek Letters//EN"
		"iso-grk2.ent" "ISO 8879:1986//ENTITIES Monotoniko Greek//EN"
		"iso-grk3.ent" "ISO 8879:1986//ENTITIES Greek Symbols//EN"
		"iso-grk4.ent" "ISO 8879:1986//ENTITIES Alternative Greek Symbols//EN"
		"iso-lat1.ent" "ISO 8879:1986//ENTITIES Added Latin 1//EN"
		"iso-lat2.ent" "ISO 8879:1986//ENTITIES Added Latin 2//EN"
		"iso-num.ent" "ISO 8879:1986//ENTITIES Numeric and Special Graphic//EN"
		"iso-pub.ent" "ISO 8879:1986//ENTITIES Publishing//EN"
		"iso-tech.ent" "ISO 8879:1986//ENTITIES General Technical//EN"
	)

	# here are the entities available; assume no spaces in filenames...
	avail=($(ls "${ROOT}${isodir}" | sort))

	# double-check the lists
	verb "  Populating ${CATALOG} with ISO DocBook entities"
	i=0 ; j=0
	while [[ ${i} -lt ${#entities[@]} || ${j} -lt ${#avail[@]} ]]; do
		if [[ ${i} -ge ${#entities[@]} ]]; then
			echo "Warning: Extra ISO entities file: ${avail[j]}"
			let j=j+1
		elif [[ ${j} -ge ${#avail[@]} ]]; then
			echo "Warning: Entities file not found: ${entities[i]}"
			let i=i+2
		elif [[ ${avail[j]} < ${entities[i]} ]]; then
			echo "Warning: Extra ISO entities file: ${avail[j]}"
			let j=j+1
		elif [[ ${entities[i]} < ${avail[j]} ]]; then
			echo "Warning: Entities file not found: ${entities[i]}"
			let i=i+2
		elif [[ ${entities[i]} == ${avail[j]} ]]; then
			xmlcatalog --noout --add "public" "${entities[i+1]}" \
				"file://${isodir}/${entities[i]}" "${ROOT}${CATALOG}"
			let j=j+1
			let i=i+2
		else
			error "${0}: whoah, shouldn't be here"
		fi
	done
}

#
# populate XSL stylesheets
#
populate_xsl() {
	local f adds

	# This is either xsl, xsl-ns, xsl-saxon or xsl-xalan
	local type=$1

	# Delete current entries from the catalog (delete legacy versioned entries too)
	clean_catalog "${DOCBOOKDIR}/${type}-stylesheets(-[0-9\.]+)?" "${CATALOG}"
	clean_catalog "${DOCBOOKDIR}/${type}-stylesheets(-[0-9\.]+)?" "${ROOTCATALOG}"

	local xsldir=${DOCBOOKDIR}/${type}-stylesheets

	if [[ ! -d ${ROOT}${xsldir} ]] ; then
		echo "DocBook XSL stylesheets (${type}) not found" >&2
		return 1
	fi

	if [[ ! -e ${ROOT}${xsldir}/html/docbook.xsl || ! -e ${ROOT}${xsldir}/common/l10n.xml ]] ; then
		echo "DocBook XSL stylesheets are missing files from ${xsldir}" >&2
		return 1
	fi

	# Populate catalog with XSL entries
	echo "Found DocBook XSL stylesheets (${type}) in ${xsldir}"

	verb "  Populating ${ROOTCATALOG} with XSL delegations"
	adds=(
		"delegateSystem"  "http://docbook.sourceforge.net/release/${type}/"  "${CATALOG}"
		"delegateURI"     "http://docbook.sourceforge.net/release/${type}/"  "${CATALOG}"
	)
	multi_xmlcatalog_add "${ROOTCATALOG}" "${adds[@]}"

	verb "  Populating ${CATALOG} with XSL stylesheets"
	adds=(
		"rewriteSystem"  "http://docbook.sourceforge.net/release/${type}/current"  "${xsldir}"
		"rewriteURI"     "http://docbook.sourceforge.net/release/${type}/current"  "${xsldir}"
	)
	multi_xmlcatalog_add "${CATALOG}" "${adds[@]}"
}

# Call the main routine
main "$@"
