#!/bin/sh
# yelp-check
# Copyright (C) 2011 Shaun McCance <shaunm@gnome.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

xsl_mal_link='/usr/local/share/yelp-xsl/xslt/mallard/common/mal-link.xsl'
xsl_mal_license='/usr/local/share/yelp-tools/xslt/mal-license.xsl'
xsl_mal_status='/usr/local/share/yelp-tools/xslt/mal-status.xsl'
xsl_mal_rng='/usr/local/share/yelp-tools/xslt/mal-rng.xsl'
xsl_comments='/usr/local/share/yelp-tools/xslt/comments.xsl'
xsl_media='/usr/local/share/yelp-tools/xslt/media.xsl'

urlencode () {
    # We usually don't want to urlencode slashes, because we're
    # usually converting file paths to URIs. But we do want to
    # urlencode slases for names of RNG files in validate_page.
    if [ "x$2" = "x/" ]; then
        urlencode_slash=''
    else
        urlencode_slash='\/'
    fi
    echo "$1" | LC_ALL=C awk '
BEGIN {
  for (i = 1; i <= 255; i++) chars[sprintf("%c", i)] = i;
}
{
  ret = "";
  for (i = 1; i <= length($0); i++) {
    c = substr($0, i, 1);
    if (c ~ /['$urlencode_slash'a-zA-Z0-9._-]/)
      ret = ret c;
    else
      ret = ret sprintf("%%%X%X", int(chars[c] / 16), chars[c] % 16);
  }
  print ret;
}'
}

urldecode () {
    echo "$1" | LC_ALL=C awk '
BEGIN {
  for(i = 0; i < 10; i++) hex[i] = i;
  hex["A"] = hex["a"] = 10;
  hex["B"] = hex["b"] = 11;
  hex["C"] = hex["c"] = 12;
  hex["D"] = hex["d"] = 13;
  hex["E"] = hex["e"] = 14;
  hex["F"] = hex["f"] = 15;
}
{
  ret = "";
  for (i = 1; i <= length($0); i++) {
    c = substr($0, i, 1);
    if (c == "+") {
      ret = ret " ";
    }
    else if (c == "%") {
      c = sprintf("%c", hex[substr($0, i + 1, 1)] * 16 + hex[substr($0, i + 2, 1)]);
      ret = ret c;
      i += 2;
    }
    else {
      ret = ret c;
    }
  }
  print ret;
}'
}

docbook_version='
<xsl:stylesheet
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:db="http://docbook.org/ns/docbook"
    version="1.0">
<xsl:output method="text"/>
<xsl:template match="/">
  <xsl:choose>
    <xsl:when test="/db:*/@version">
      <xsl:value-of select="/db:*/@version"/>
    </xsl:when>
    <xsl:when test="/db:*">
      <xsl:text>5.0</xsl:text>
    </xsl:when>
    <xsl:otherwise>
      <xsl:text>4</xsl:text>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>
</xsl:stylesheet>
'

yelp_usage () {
    (
        echo "Usage: yelp-check <COMMAND> [OPTIONS] [FILES]"
        echo ""
        echo "Commands:"
        echo "  comments      Print the editorial comments in a document"
        echo "  hrefs         Find broken external links in a document"
        echo "  ids           Find Mallard page IDs that do not match file names"
	echo "  license       Report the license of Mallard pages"
        echo "  links         Find broken xref or linkend links in a document"
        echo "  media         Find broken references to media files"
        echo "  orphans       Find orphaned pages in a Mallard document"
        echo "  status        Report the status of Mallard pages"
        echo "  validate      Validate files against a DTD or RNG"
    ) 1>&2
}
yelp_usage_hrefs () {
    (
        echo "Usage: yelp-check hrefs <FILES>"
        echo ""
        echo "  Find broken href links in FILES in a Mallard document, or"
        echo "  broken ulink or XLink links in FILES in a DocBook document."
    ) 1>&2
}
yelp_usage_ids () {
    (
        echo "Usage: yelp-check ids <FILES>"
        echo ""
        echo "  Find pages in a Mallard document whose page ID does not match"
        echo "  the base file name of the page file."
    ) 1>&2
}
yelp_usage_links () {
    (
        echo "Usage: yelp-check links <FILES>"
        echo ""
        echo "  Find broken xref links in FILES in a Mallard document,"
        echo "  or broken linkend links in FILES in a DocBook document."
        echo ""
        echo "Options:"
        echo "  -c CACHE      Use the existing Mallard cache CACHE"
        echo "  -i            Ignore xrefs where href is present"
    ) 1>&2
}
yelp_usage_media () {
    (
        echo "Usage: yelp-check media <FILES>"
        echo ""
        echo "  Find broken references to media files. In Mallard, this"
        echo "  checks media and thumb elements. In DocBook, this checks"
        echo "  audiodata, imagedata, and videodata elements."
    ) 1>&2
}
yelp_usage_orphans () {
    (
        echo "Usage: yelp-check orphans <FILES>"
        echo ""
        echo "  Locate orphaned pages among FILES in a Mallard document."
        echo "  Orphaned pages are any pages that cannot be reached by"
        echo "  topic links alone from the index page."
        echo ""
        echo "Options:"
        echo "  -c CACHE      Use the existing Mallard cache CACHE"
    ) 1>&2
}
yelp_usage_comments () {
    (
        echo "Usage: yelp-check comments <FILES>"
        echo ""
        echo "  Print the editorial comments in the files FILES, using the"
        echo "  comment element in Mallard and the remark element in DocBook."
    ) 1>&2
}
yelp_usage_license () {
    (
        echo "Usage: yelp-check license <FILES>"
        echo ""
        echo "  Report the license of the Mallard page files FILES. Each"
        echo "  matching page is reporting along with its license, reported"
	echo "  based on the href attribute of the license element. Common"
	echo "  licenses use a shortened identifier. Pages with multiple"
	echo "  licenses have the identifiers separated by spaces. Pages"
	echo "  with no license element report 'none'. Licenses with no"
	echo "  href attribute are reported as 'unknown'."
        echo ""
        echo "Options:"
        echo "  --only LICENSES     Only show pages whose license is in LICENSES"
        echo "  --except LICENSES   Exclude pages whose license is in LICENSES"
        echo "  --totals            Show total counts for each license"
        echo "LICENSES may be a comma- and/or space-separated list."
    ) 1>&2
}
yelp_usage_status () {
    (
        echo "Usage: yelp-check status <FILES>"
        echo ""
        echo "  Report the status of the Mallard page files FILES. Each"
        echo "  matching page is reporting along with its status."
        echo ""
        echo "Options:"
        echo "  --version VER       Select revisions with the version attribute VER"
        echo "  --docversion VER    Select revisions with the docversion attribute VER"
        echo "  --pkgversion VER    Select revisions with the pkgversion attribute VER"
        echo "  --older DATE        Only show pages older than DATE"
        echo "  --newer DATE        Only show pages newer than DATE"
        echo "  --only STATUSES     Only show pages whose status is in STATUSES"
        echo "  --except STATUSES   Exclude pages whose status is in STATUSES"
        echo "  --totals            Show total counts for each status"
        echo "VER and STATUSES may be comma- and/or space-separated lists."
    ) 1>&2
}
yelp_usage_validate () {
    (
        echo "Usage: yelp-check validate <FILES>"
        echo ""
        echo "  Validate FILES against the appropriate DTD or RNG."
        echo "  For Mallard pages, perform automatic RNG merging"
        echo "  based on the version attribute."
        echo ""
        echo "Options:"
        echo "  --strict            Disallow unknown namespaces"
        echo "  --allow NS          Explicitly allow namespace NS in strict mode"
    ) 1>&2
}

if [ $# = 0 ]; then
    yelp_usage
    exit 1
fi

yelp_hrefs_db () {
    (
        echo '<xsl:stylesheet'
        echo ' xmlns:xsl="http://www.w3.org/1999/XSL/Transform"'
        echo ' xmlns:db="http://docbook.org/ns/docbook"'
        echo ' xmlns:xlink="www.w3.org/1999/xlink"'
        echo ' version="1.0">'
        echo '<xsl:output method="text"/>'
        echo '<xsl:template match="/">'
        echo '<xsl:for-each select="//ulink/@url | //*/xlink:href">'
        echo '<xsl:if test="not(starts-with(string(.), '\''mailto:'\''))">'
        echo '<xsl:value-of select="(ancestor-or-self::*/@id | ancestor-or-self::*/@xml:id)[last()]"/>'
        echo '<xsl:text> </xsl:text>'
        echo '<xsl:value-of select="string(.)"/>'
        echo '<xsl:text>&#x000A;</xsl:text>'
        echo '</xsl:if>'
        echo '</xsl:for-each>'
        echo '</xsl:template>'
        echo '</xsl:stylesheet>'
    ) | xsltproc --xinclude - "$1"
}

yelp_hrefs_page () {
    (
        echo '<xsl:stylesheet'
        echo ' xmlns:xsl="http://www.w3.org/1999/XSL/Transform"'
        echo ' xmlns:mal="http://projectmallard.org/1.0/"'
        echo ' version="1.0">'
        echo '<xsl:output method="text"/>'
        echo '<xsl:template match="/mal:page">'
        echo '<xsl:for-each select="//*[@href]">'
        echo '<xsl:if test="not(starts-with(@href, '\''mailto:'\''))">'
        echo '<xsl:value-of select="/mal:page/@id"/>'
        echo '<xsl:text> </xsl:text>'
        echo '<xsl:value-of select="@href"/>'
        echo '<xsl:text>&#x000A;</xsl:text>'
        echo '</xsl:if>'
        echo '</xsl:for-each>'
        echo '</xsl:template>'
        echo '</xsl:stylesheet>'
    ) | xsltproc --xinclude - "$1"
}

yelp_hrefs () {
    if [ "$#" = "0" -o "x$1" = "x--help" ]; then
        yelp_usage_hrefs
        exit 1
    fi
    check_out_file=`mktemp "${TMPDIR:-/tmp}"/yelp-XXXXXXXX`
    for xml in "$@"; do
        if [ -d "$xml" ]; then
            for page in "$xml"/*.page; do
                yelp_hrefs_page "$page"
            done
        else
            ext=`echo "$1" | sed -e 's/.*\.//'`
            if [ "x$ext" = "xxml" -o "x$ext" = "xdocbook" ]; then
                yelp_hrefs_db "$xml"
            else
                yelp_hrefs_page "$xml"
            fi
        fi | while read id url; do
            echo "`dirname $xml`" "$id" "$url"
        done
    done | sort | uniq | while read base id url; do
        colon=`echo "$url" | cut -d: -f1`
        if [ "x$colon" = "x$url" ]; then
            test -f "$base/`urldecode $url`" || echo "$id: $url"
        else
            (curl -s -I -L "$url" | grep '^HTTP/' | tail -n 1 | head -n 1 | grep -q 'HTTP/.\.. 200 .*') ||
            echo "$id: $url"
        fi
    done > "$check_out_file"
    ret=`cat "$check_out_file" | wc -l`
    if test "x$ret" != "x0"; then ret="1"; fi
    cat "$check_out_file"
    rm "$check_out_file"
    exit $ret
}

yelp_ids_page () {
    pageid=$(
    (
        echo '<xsl:stylesheet'
        echo ' xmlns:xsl="http://www.w3.org/1999/XSL/Transform"'
        echo ' xmlns:mal="http://projectmallard.org/1.0/"'
        echo ' version="1.0">'
        echo '<xsl:output method="text"/>'
        echo '<xsl:template match="/mal:page">'
        echo '<xsl:value-of select="@id"/>'
        echo '</xsl:template>'
        echo '</xsl:stylesheet>'
    ) | xsltproc --xinclude - "$1")
    if [ "$pageid.page" != `basename "$1"` ]; then
        if [ `dirname "$1"` = '.' ]; then
            echo `basename "$1"`": $pageid"
        else
            echo "$1: $pageid"
        fi
        exit 1
    fi
}

yelp_ids () {
    if [ "$#" = "0" -o "x$1" = "x--help" ]; then
        yelp_usage_ids
        exit 1
    fi
    ret=0
    for xml in "$@"; do
        if [ -d "$xml" ]; then
            for page in "$xml"/*.page; do
                yelp_ids_page "$page" || ret=$?
            done
        else
            yelp_ids_page "$xml" || ret=$?
        fi
    done
    exit $ret
}

yelp_links_db () {
    (
        echo '<xsl:stylesheet'
        echo ' xmlns:xsl="http://www.w3.org/1999/XSL/Transform"'
        echo ' xmlns:db="http://docbook.org/ns/docbook"'
        echo ' xmlns:exsl="http://exslt.org/common"'
        echo ' extension-element-prefixes="exsl"'
        echo ' version="1.0">'
        echo '<xsl:output method="text"/>'
        echo '<xsl:key name="idkey" match="*[@id or @xml:id]" use="@id | @xml:id"/>'
        echo '<xsl:template match="/">'
        echo ' <xsl:for-each select="//*[@linkend]">'
        echo '  <xsl:if test="not(key('"'idkey'"', @linkend))">'
        echo '   <xsl:value-of select="(ancestor-or-self::*/@id | ancestor-or-self::*/@xml:id)[last()]"/>'
        echo '   <xsl:text>: </xsl:text>'
        echo '   <xsl:value-of select="@linkend"/>'
        echo '   <xsl:text>&#x000A;</xsl:text>'
        echo '  </xsl:if>'
        echo ' </xsl:for-each>'
        echo '</xsl:template>'
        echo '</xsl:stylesheet>'
    ) | xsltproc --xinclude - "$1"
}

yelp_links_page () {
    (
        echo '<xsl:stylesheet'
        echo ' xmlns:xsl="http://www.w3.org/1999/XSL/Transform"'
        echo ' xmlns:mal="http://projectmallard.org/1.0/"'
        echo ' xmlns:exsl="http://exslt.org/common"'
        echo ' extension-element-prefixes="exsl"'
        echo ' version="1.0">'
        xsl='file://'`urlencode "$xsl_mal_link"`
        echo '<xsl:import href="'"$xsl"'"/>'
        check_cache_url='file://'`urlencode "$check_cache_file"`
        echo '<xsl:param name="mal.cache.file" select="'"'$check_cache_url'"'"/>'
        echo '<xsl:output method="text"/>'
        echo '<xsl:template match="/mal:page">'
        echo ' <xsl:variable name="page" select="@id"/>'
        if [ "x$check_links_ignore" = "x1" ]; then
            echo ' <xsl:for-each select="//*[@xref][not(@href)]">'
        else
            echo ' <xsl:for-each select="//*[@xref]">'
        fi
        echo '  <xsl:variable name="xref" select="@xref"/>'
        echo '  <xsl:variable name="linkid">'
        echo '   <xsl:call-template name="mal.link.xref.linkid"/>'
        echo '  </xsl:variable>'
        echo '  <xsl:for-each select="$mal.cache">'
        echo '   <xsl:variable name="target" select="key('"'mal.cache.key'"', $linkid)"/>'
        echo '   <xsl:if test="count($target) = 0">'
        echo '    <xsl:value-of select="$page"/>'
        echo '    <xsl:text>: </xsl:text>'
        echo '    <xsl:value-of select="$xref"/>'
        echo '    <xsl:text>&#x000A;</xsl:text>'
        echo '   </xsl:if>'
        echo '  </xsl:for-each>'
        echo ' </xsl:for-each>'
        echo '</xsl:template>'
        echo '</xsl:stylesheet>'
    ) | xsltproc --xinclude - "$1"
}

yelp_links () {
    if [ "$#" = "0" -o "x$1" = "x--help" ]; then
        yelp_usage_links
        exit 1
    fi
    while [ "$#" != "0" ]; do
        case "$1" in
            "-c")
                shift
                check_cache_file="$1"
                shift
                ;;
            "-i")
                shift
                check_links_ignore="1"
                ;;
            *)
                break
                ;;
        esac
    done
    if [ "$#" = "0" -o "x$1" = "x--help" ]; then
        yelp_usage_links
        exit 1
    fi
    if [ "x$check_cache_file" != "x" ]; then
        check_cache_file=`(cd $(dirname "$check_cache_file") && pwd)`/`basename "$check_cache_file"`
    fi
    check_out_file=`mktemp "${TMPDIR:-/tmp}"/yelp-XXXXXXXX`
    for xml in "$@"; do
        if [ -d "$xml" ]; then
            if [ "x$check_cache_file" = "x" ]; then
                check_cache_file_is_tmp="yes"
                check_cache_file=`mktemp "${TMPDIR:-/tmp}"/yelp-XXXXXXXX`
                yelp-build cache -o "$check_cache_file" "$@"
            fi
            for page in "$xml"/*.page; do
                yelp_links_page "$page"
            done
        else
            ext=`echo "$1" | sed -e 's/.*\.//'`
            if [ "x$ext" = "xxml" -o "x$ext" = "xdocbook" ]; then
                yelp_links_db "$xml"
            else
                if [ "x$check_cache_file" = "x" ]; then
                    check_cache_file_is_tmp="yes"
                    check_cache_file=`mktemp "${TMPDIR:-/tmp}"/yelp-XXXXXXXX`
                    yelp-build cache -o "$check_cache_file" "$@"
                fi
                yelp_links_page "$xml"
            fi
        fi
    done > "$check_out_file"
    ret=`cat "$check_out_file" | wc -l`
    if test "x$ret" != "x0"; then ret="1"; fi
    cat "$check_out_file"
    if [ "x$check_cache_file_is_tmp" = "xyes" ]; then
        rm "$check_cache_file"
    fi
    rm "$check_out_file"
    exit $ret
}

yelp_media_page () {
    ext=`echo "$1" | sed -e 's/.*\.//'`
    bname=`basename "$1" ".$ext"`
    xsltproc --stringparam basename "$bname" "$xsl_media" "$1"
}

yelp_media () {
    if [ "$#" = "0" -o "x$1" = "x--help" ]; then
        yelp_usage_media
        exit 1
    fi
    for xml in "$@"; do
        if [ -d "$xml" ]; then
            for page in "$xml"/*.page; do
                yelp_media_page "$page"
            done
        else
            yelp_media_page "$xml"
        fi
    done | while read line; do
        src=$(urldecode $(echo "$line" | cut -d' ' -f2))
        if [ ! -f "$src" ]; then
            echo $line
        fi
    done
}

yelp_orphans_page () {
    (
        echo '<xsl:stylesheet'
        echo ' xmlns:xsl="http://www.w3.org/1999/XSL/Transform"'
        echo ' xmlns:mal="http://projectmallard.org/1.0/"'
        echo ' xmlns:exsl="http://exslt.org/common"'
        echo ' extension-element-prefixes="exsl"'
        echo ' version="1.0">'
        xsl='file://'`urlencode "$xsl_mal_link"`
        echo '<xsl:import href="'"$xsl"'"/>'
        check_cache_url='file://'`urlencode "$check_cache_file"`
        echo '<xsl:param name="mal.cache.file" select="'"'$check_cache_url'"'"/>'
        echo '<xsl:output method="text"/>'
        echo '<xsl:template match="/mal:page">'
        echo ' <xsl:variable name="trails">'
        echo '  <xsl:call-template name="mal.link.linktrails"/>'
        echo ' </xsl:variable>'
        echo ' <xsl:if test="@id != '"'index'"' and count(exsl:node-set($trails)/*) = 0">'
        echo '  <xsl:value-of select="@id"/>'
        echo '  <xsl:text>&#x000A;</xsl:text>'
        echo ' </xsl:if>'
        echo '</xsl:template>'
        echo '</xsl:stylesheet>'
    ) | xsltproc --xinclude - "$1"
}

yelp_orphans () {
    if [ "$#" = "0" -o "x$1" = "x--help" ]; then
        yelp_usage_orphans
        exit 1
    fi
    while [ "$#" != "0" ]; do
        case "$1" in
            "-c")
                shift
                check_cache_file="$1"
                shift
                ;;
            *)
                break
                ;;
        esac
    done
    if [ "$#" = "0" -o "x$1" = "x--help" ]; then
        yelp_usage_orphans
        exit 1
    fi
    if [ "x$check_cache_file" != "x" ]; then
        check_cache_file=`(cd $(dirname "$check_cache_file") && pwd)`/`basename "$check_cache_file"`
    else
        check_cache_file_is_tmp="yes"
        check_cache_file=`mktemp "${TMPDIR:-/tmp}"/yelp-XXXXXXXX`
        yelp-build cache -o "$check_cache_file" "$@"
    fi
    check_out_file=`mktemp "${TMPDIR:-/tmp}"/yelp-XXXXXXXX`
    for xml in "$@"; do
        if [ -d "$xml" ]; then
            for page in "$xml"/*.page; do
                yelp_orphans_page "$page"
            done
        else
            yelp_orphans_page "$xml"
        fi
    done > "$check_out_file"
    ret=`cat "$check_out_file" | wc -l`
    if test "x$ret" != "x0"; then ret="1"; fi
    cat "$check_out_file"
    if [ "x$check_cache_file_is_tmp" = "xyes" ]; then
        rm "$check_cache_file"
    fi
    rm "$check_out_file"
    exit $ret
}

yelp_comments_page () {
    ext=`echo "$1" | sed -e 's/.*\.//'`
    bname=`basename "$1" ".$ext"`
    xsltproc --stringparam basename "$bname" "$xsl_comments" "$1"
}

yelp_comments () {
    if [ "$#" = "0" -o "x$1" = "x--help" ]; then
        yelp_usage_comments
        exit 1
    fi
    for xml in "$@"; do
        if [ -d "$xml" ]; then
            for page in "$xml"/*.page; do
                yelp_comments_page "$page"
            done
        else
            yelp_comments_page "$xml"
        fi
    done
}

yelp_license_page () {
    xsltproc --xinclude \
        --stringparam only "$check_only" \
        --stringparam except "$check_except" \
        --stringparam totals "$check_totals" \
        "$xsl_mal_license" "$1"
}

yelp_license () {
    if [ "$#" = "0" -o "x$1" = "x--help" ]; then
        yelp_usage_license
        exit 1
    fi
    while [ "$#" != "0" ]; do
        case "$1" in
            "--only")
                shift
                check_only="$1"
                shift
                ;;
            "--except")
                shift
                check_except="$1"
                shift
                ;;
            "--totals")
                check_totals="1"
                shift
                ;;
            *)
                break
                ;;
        esac
    done
    if [ "$#" = "0" -o "x$1" = "x--help" ]; then
        yelp_usage_license
        exit 1
    fi
    for xml in "$@"; do
        ext=`echo "$1" | sed -e 's/.*\.//'`
        if [ -d "$xml" ]; then
            for page in "$xml"/*.page; do
                yelp_license_page "$page" || gret="$?"
            done
        elif [ "x$ext" = "xpage" -o "x$ext" = "xstub" -o "x$ext" = "xcache" ]; then
            yelp_license_page "$xml" || gret="$?"
        else
	    echo "Unrecognized page " $xml 2>&1
	    exit 1
	fi
    done | if [ "x$check_totals" = "x1" ]; then
	sort | uniq -c | sed -e 's/^ *//' | awk '{print $2 ": " $1}'
    else
	sort
    fi
    return 0
}

yelp_status () {
    if [ "$#" = "0" -o "x$1" = "x--help" ]; then
        yelp_usage_status
        exit 1
    fi
    while [ "$#" != "0" ]; do
        case "$1" in
            "--version")
                shift
                check_version="$1"
                shift
                ;;
            "--docversion")
                shift
                check_docversion="$1"
                shift
                ;;
            "--pkgversion")
                shift
                check_pkgversion="$1"
                shift
                ;;
            "--older")
                shift
                check_older="$1"
                shift
                ;;
            "--newer")
                shift
                check_newer="$1"
                shift
                ;;
            "--only")
                shift
                check_only="$1"
                shift
                ;;
            "--except")
                shift
                check_except="$1"
                shift
                ;;
            "--totals")
                check_totals="1"
                shift
                ;;
            *)
                break
                ;;
        esac
    done
    if [ "$#" = "0" -o "x$1" = "x--help" ]; then
        yelp_usage_status
        exit 1
    fi
    check_cache_file=`mktemp "${TMPDIR:-/tmp}"/yelp-XXXXXXXX`
    yelp-build cache -o "$check_cache_file" "$@"
    xsltproc \
        --stringparam version "$check_version" \
        --stringparam docversion "$check_docversion" \
        --stringparam pkgversion "$check_pkgversion" \
        --stringparam newer "$check_newer" \
        --stringparam older "$check_older" \
        --stringparam only "$check_only" \
        --stringparam except "$check_except" \
        --stringparam totals "$check_totals" \
        "$xsl_mal_status" "$check_cache_file"
    rm "$check_cache_file"
    return 0
}

yelp_validate_page () {
    # Using temp files because pipes create subshells, making it really
    # hard to return the right exit status in a portable way.
    if [ "x$check_rng_dir" = "x" ]; then
        check_rng_dir=`mktemp -d "${TMPDIR:-/tmp}"/yelp-XXXXXXXX`
    fi
    check_out_file=`mktemp "${TMPDIR:-/tmp}"/yelp-XXXXXXXX`
    check_rng_file=`(
            echo '<xsl:stylesheet'
            echo ' xmlns:cache="http://projectmallard.org/cache/1.0/"'
            echo ' xmlns:xsl="http://www.w3.org/1999/XSL/Transform"'
            echo ' version="1.0">'
            echo '<xsl:output method="text"/>'
            echo '<xsl:template match="/*">'
            echo '<xsl:choose>'
            echo '<xsl:when test="string(@version) != '"''"'">'
            echo '<xsl:value-of select="@version"/>'
            echo '</xsl:when>'
            echo '<xsl:when test="/cache:cache">'
            echo '<xsl:text>cache/1.0 1.0</xsl:text>'
            echo '</xsl:when>'
            echo '<xsl:otherwise>'
            echo '<xsl:text>1.0</xsl:text>'
            echo '</xsl:otherwise>'
            echo '</xsl:choose>'
            echo '</xsl:template>'
            echo '</xsl:stylesheet>'
            ) | xsltproc - "$1"`
    check_rng_file=`urlencode "$check_rng_file" /`.rng
    if [ ! -f "$check_rng_dir/$check_rng_file" ]; then
        # If we've already made an RNG file for this version string, don't
        # do it again. We've urlencoded the file name + slashes, because
        # version strings often contain slashes. But xsltproc treats the
        # -o option as a URL and urldecodes, so doubly urlencode, because
        # we want the urlencoded string to be the on-disk name.
        xsltproc -o "$check_rng_dir/"`urlencode "$check_rng_file"` \
            --param rng.strict "$check_strict" \
            --stringparam rng.strict.allow "$check_strict_allow" \
            "$xsl_mal_rng" "$1"
    fi
    xmllint --noout --xinclude --noent --relaxng "$check_rng_dir/$check_rng_file" "$1" > "$check_out_file" 2>&1
    ret="$?"
    cat "$check_out_file" | grep -v 'validates$'
    rm "$check_out_file"
    return $ret;
}

yelp_validate () {
    if [ "$#" = "0" -o "x$1" = "x--help" ]; then
        yelp_usage_validate
        exit 1
    fi
    gret="0"
    check_strict="false()"
    check_strict_allow=""
    while [ "$#" != "0" ]; do
        case "$1" in
            "--strict")
                check_strict="true()"
                shift
                ;;
            "--allow")
                shift
                check_strict_allow="$check_strict_allow $1"
                shift
                ;;
            *)
                break
                ;;
        esac
    done
    for xml in "$@"; do
        ext=`echo "$1" | sed -e 's/.*\.//'`
        if [ -d "$xml" ]; then
            for page in "$xml"/*.page; do
                yelp_validate_page "$page" || gret="$?"
            done
        elif [ "x$ext" = "xpage" -o "x$ext" = "xstub" -o "x$ext" = "xcache" ]; then
            yelp_validate_page "$xml" || gret="$?"
        else
            version=`echo "$docbook_version" | xsltproc - "$xml"`
            major=`echo "$version" | cut -c1`
            if [ "x$major" = "x5" ]; then
                check_out_file=`mktemp "${TMPDIR:-/tmp}"/yelp-XXXXXXXX`
                rng_uri="http://docbook.org/xml/$version/rng/docbook.rng"
                xmllint --noout --xinclude --noent --relaxng "$rng_uri" "$xml" > "$check_out_file" 2>&1
                gret="$?"
                cat "$check_out_file" | grep -v 'validates$'
                rm "$check_out_file"
            elif xmllint --nocdata "$xml" | grep -q '<!DOCTYPE'; then
                xmllint --noout --xinclude --noent --postvalid "$xml" || gret="$?"
            else
                dtd_uri='http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd'
                xmllint --noout --xinclude --noent --dtdvalid "$dtd_uri" "$xml" || gret="$?"
            fi
        fi
    done
    if [ "x$check_rng_dir" != "x" ]; then
        rm "$check_rng_dir"/*.rng
        rmdir "$check_rng_dir"
    fi
    exit "$gret"
}

cmd="$1"
shift
case "x$cmd" in
    "xcomments")
        yelp_comments "$@"
        ;;
    "xhrefs")
        yelp_hrefs "$@"
        ;;
    "xids")
        yelp_ids "$@"
        ;;
    "xlinks")
        yelp_links "$@"
        ;;
    "xorphans")
        yelp_orphans "$@"
        ;;
    "xlicense")
        yelp_license "$@"
        ;;
    "xmedia")
        yelp_media "$@"
        ;;
    "xstatus")
        yelp_status "$@"
        ;;
    "xvalidate")
        yelp_validate "$@"
        ;;
    *)
        yelp_usage
        ;;
esac
