#!/bin/sh
# -*- indent-tabs-mode: nil -*-
#
# yelp-build
# Copyright (C) 2010-2015 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_cache='/usr/share/yelp-xsl/xslt/mallard/cache/mal-cache.xsl'
xsl_db2html='/usr/share/yelp-xsl/xslt/docbook/html/db2html.xsl'
xsl_db2xhtml='/usr/share/yelp-xsl/xslt/docbook/html/db2xhtml.xsl'
xsl_mal2html='/usr/share/yelp-xsl/xslt/mallard/html/mal2html.xsl'
xsl_mal2xhtml='/usr/share/yelp-xsl/xslt/mallard/html/mal2xhtml.xsl'
yelp_icon_dir='/usr/share/yelp-xsl/icons/hicolor/24x24/status'
yelp_js_dir='/usr/share/yelp-xsl/js'
xsl_mal_epub='/usr/share/yelp-tools/xslt/mal-epub.xsl'
xsl_mal_opf='/usr/share/yelp-tools/xslt/mal-opf.xsl'
xsl_mal_ncx='/usr/share/yelp-tools/xslt/mal-ncx.xsl'
xsl_mal_files='/usr/share/yelp-tools/xslt/mal-files.xsl'

mkdir_p () {
    if [ ! -d "$1" ]; then
        mkdir_p `dirname "$1"`
        mkdir "$1"
    fi
}

urlencode () {
    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 ~ /[\/a-zA-Z0-9._-]/)
      ret = ret c;
    else
      ret = ret sprintf("%%%X%X", int(chars[c] / 16), chars[c] % 16);
  }
  print ret;
}'
}

urldecode () {
    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;
}'
}

yelp_usage () {
    (
        echo "Usage: yelp-build <COMMAND> [OPTIONS] [FILES]"
        echo ""
        echo "Commands:"
        echo "  cache         Create a Mallard cache file"
        echo "  epub          Create an EPUB file for Mallard"
        echo "  html          Convert input files to HTML"
        echo "  xhtml         Convert input files to XHTML"
    ) 1>&2
}
yelp_usage_cache () {
    (
        echo "Usage: yelp-build cache <FILES>"
        echo ""
        echo "  Create a Mallard cache file from the page files FILES."
        echo "  If FILES contains directories, all .page files in those"
        echo "  directories will be used."
        echo ""
        echo "Options:"
        echo "  -o OUT        Output cache to the file OUT"
        echo "  -s            Treat pages as belonging to a Mallard site"
    ) 1>&2
}
yelp_usage_epub () {
    (
        echo "Usage: yelp-build epub [OPTIONS] <FILES>"
        echo ""
        echo "  Create and EPUB file from the Mallard page files FILES."
        echo ""
        echo "Options:"
        echo "  -c CACHE      Use the existing Mallard cache CACHE"
        echo "  -o OUT        Output en EPUB file named OUT"
        echo "  -x CUSTOM     Import the custom XSLT file CUSTOM"
        echo "  -p PATHS      Extra paths to search for files"
        echo "  -i            Ignore missing media files"
    ) 1>&2
}
yelp_usage_html () {
    (
        echo "Usage: yelp-build <html|xhtml> [OPTIONS] <FILES>"
        echo ""
        echo "  Create HTML or XHTML from the input files FILES."
        echo "  FILES can be DocBook files, Mallard page files,"
        echo "  or directories containing Mallard page files."
        echo ""
        echo "Options:"
        echo "  -c CACHE      Use the existing Mallard cache CACHE"
        echo "  -o OUT        Output files in the directory OUT"
        echo "  -x CUSTOM     Import the custom XSLT file CUSTOM"
        echo "  -p PATHS      Extra paths to search for files"
        echo "  -i            Ignore missing media files"
    ) 1>&2
}

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

yelp_paths_normalize () {
    for path in $(echo "$1" | sed -e 's/[: ]/\n/g'); do
        (cd "$path" && pwd)
    done | tr '\n' ' '
}

yelp_cache_in_page () {
    fbase=$(basename "$1")
    ext=$(echo "$fbase" | sed -e 's/.*\.//')
    fdir=$( (cd $(dirname "$1") && pwd) )
    sdir=${fdir##${cache_site_root}}/
    url=file://$(echo "$fdir/$fbase" | urlencode)
    if [ "x$cache_site" = "x1" ]; then
        siteattr=' site:dir="'"$sdir"'"'
    fi
    if [ "x$ext" = "xstack" ]; then
        echo '<stack cache:href="'"$url"'"'"$siteattr"'/>'
    else
        echo '<page cache:href="'"$url"'"'"$siteattr"'/>'
    fi
}

yelp_cache_in_site () {
    for dir in "$1"/*; do
        if [ -d "$dir" ]; then
            if [ $(basename "$dir") != "__pintail__" ]; then
                yelp_cache_in_site "$dir"
            fi
        fi
    done
    for page in "$1"/*.page "$1"/*.stack; do
        if [ -e "$page" ]; then
            yelp_cache_in_page "$page"
        fi
    done
}

yelp_cache_in () {
    echo '<cache:cache xmlns:cache="http://projectmallard.org/cache/1.0/"'
    if [ "x$cache_site" = "x1" ]; then
        echo '  xmlns:site="http://projectmallard.org/site/1.0/"'
    fi
    echo '  xmlns="http://projectmallard.org/1.0/">'
    for page in "$@"; do
        if [ -d "$page" ]; then
            if [ "x$cache_site" = "x1" ]; then
                yelp_cache_in_site "$page"
            else
                for sub in "$page"/*.page "$page"/*.stack; do
                    if [ -e "$sub" ]; then
                        yelp_cache_in_page "$sub"
                    fi
                done
            fi
        else
            yelp_cache_in_page "$page"
        fi
    done
    echo '</cache:cache>'
}

yelp_cache () {
    cache_out="index.cache"
    while [ "$#" != "0" ]; do
        case "$1" in
            "-o")
                shift
                cache_out="$1"
                shift
                ;;
            "-s")
                cache_site="1"
                cache_site_root=$(pwd)
                shift
                ;;
            *)
                break
                ;;
        esac
    done
    if [ "$#" = "0" -o "x$1" = "x--help" -o "x$1" = "x-h" ]; then
        yelp_usage_cache
        exit 1
    fi
    yelp_cache_in "$@" | xsltproc --path "$html_paths" \
                                  --xinclude -o "$cache_out" \
                                  "$xsl_mal_cache" -
}

yelp_html_xsl_common () {
    if [ "x$html_internal_datadir" != "x" ]; then
        echo '<xsl:variable name="yelp.internal.datadir" select="'"'$html_internal_datadir'"'"/>'
        echo '<xsl:param name="html.css.root" select="$yelp.internal.datadir"/>'
        echo '<xsl:param name="html.js.root" select="$yelp.internal.datadir"/>'
    else
        echo '<xsl:variable name="yelp.internal.datadir" select="'"''"'"/>'
    fi
    if [ "x$html_internal_xsl" != "x" ]; then
        echo '<xsl:include href="file://'`echo "$html_internal_xsl" | urlencode`'"/>'
    fi
    if [ "x$html_custom" != "x" ]; then
        echo '<xsl:include href="file://'`echo "$html_custom" | urlencode`'"/>'
    fi
    echo '<xsl:template name="html.css">'
    echo ' <xsl:param name="node" select="."/>'
    echo ' <xsl:variable name="yelp.locale">'
    echo '  <xsl:choose>'
    echo '   <xsl:when test="$node/@xml:lang != '"''"'">'
    echo '    <xsl:value-of select="$node/@xml:lang"/>'
    echo '   </xsl:when>'
    echo '   <xsl:when test="$node/@lang != '"''"'">'
    echo '    <xsl:value-of select="$node/@lang"/>'
    echo '   </xsl:when>'
    echo '   <xsl:otherwise>'
    echo '    <xsl:text>C</xsl:text>'
    echo '   </xsl:otherwise>'
    echo '  </xsl:choose>'
    echo ' </xsl:variable>'
    echo ' <exsl:document href="{$yelp.internal.datadir}{$yelp.locale}.css" method="text">'
    echo '  <xsl:call-template name="html.css.content">'
    echo '   <xsl:with-param name="node" select="$node"/>'
    echo '   <xsl:with-param name="direction">'
    echo '    <xsl:call-template name="l10n.direction">'
    echo '     <xsl:with-param name="lang" select="$yelp.locale"/>'
    echo '    </xsl:call-template>'
    echo '   </xsl:with-param>'
    echo '  </xsl:call-template>'
    echo ' </exsl:document>'
    echo ' <link rel="stylesheet" type="text/css" href="{$html.css.root}{$yelp.locale}.css"/>'
    echo '</xsl:template>'
    echo '<xsl:template name="html.js.script">'
    echo ' <xsl:param name="node" select="."/>'
    echo ' <exsl:document href="{$yelp.internal.datadir}yelp.js" method="text">'
    echo '  <xsl:call-template name="html.js.content">'
    echo '   <xsl:with-param name="node" select="$node"/>'
    echo '  </xsl:call-template>'
    echo ' </exsl:document>'
    echo ' <script type="text/javascript" src="{$html.js.root}yelp.js"/>'
    echo '</xsl:template>'
}
yelp_html_db2html () {
    for xml in "$@"; do
        xmldir=`dirname "$xml"`
        xmldir=`(cd "$xmldir" && pwd)`
        # Output HTML
        (
            echo '<xsl:stylesheet'
            echo ' xmlns:xsl="http://www.w3.org/1999/XSL/Transform"'
            echo ' xmlns:exsl="http://exslt.org/common"'
            echo ' xmlns="http://www.w3.org/1999/xhtml"'
            echo ' extension-element-prefixes="exsl"'
            echo ' version="1.0">'
            if [ "x$is_xhtml" = "x1" ]; then
                xsl='file://'`echo "$xsl_db2xhtml" | urlencode`
            else
                xsl='file://'`echo "$xsl_db2html" | urlencode`
            fi
            echo '<xsl:import href="'"$xsl"'"/>'
            yelp_html_xsl_common
            echo '</xsl:stylesheet>'
        ) | xsltproc --path "$html_paths" --xinclude -o "$html_out"/ - "$xml"
        # Copy media
        if [ "x$xmldir" != "x$html_out" ]; then
            (
                echo '<xsl:stylesheet'
                echo ' xmlns:xsl="http://www.w3.org/1999/XSL/Transform"'
                echo ' xmlns:db="http://docbook.org/ns/docbook"'
                echo ' exclude-result-prefixes="db"'
                echo ' version="1.0">'
                echo '<xsl:output method="text"/>'
                echo '<xsl:template match="/">'
                echo ' <xsl:for-each select="'
                echo '   //audiodata | //imagedata | //videodata |'
                echo '   //db:audiodata | //db:imagedata | //db:videodata">'
                echo '  <xsl:value-of select="concat(@fileref, '"'&#x000A;'"')"/>'
                echo ' </xsl:for-each>'
                echo '</xsl:template>'
                echo '</xsl:stylesheet>'
            ) | xsltproc --path "$html_paths" --xinclude - "$xml" | while read media; do
                mfile=`echo "$media" | urldecode`
                minput="$xmldir/$mfile"
                moutput="$html_out/$mfile"
                mkdir_p `dirname "$moutput"`
                if [ ! -f "$minput" -a "x$html_paths" != "x" ]; then
                    minput_rel=${minput#"$(pwd)/"}
                    if [ "x$minput_rel" != "x$minput_src" ]; then
                        for path in $html_paths; do
                            if [ -f "$path/$minput_rel" ]; then
                                minput="$path/$minput_rel"
                                break
                            fi
                        done
                    fi
                fi
                if [ -f "$minput" -o "x$html_ignore_media" != "x1" ]; then
                    cp "$minput" "$moutput"
                fi
            done
        fi
        # Copy JavaScript
        cp "${yelp_js_dir}/highlight.pack.js" "$html_out/$html_internal_datadir"
    done
}

yelp_html_mal2html () {
    if [ "x$html_cache_file" != "x" ]; then
        html_cache_file=`(cd $(dirname "$html_cache_file") && pwd)`/`basename "$html_cache_file"`
    else
        html_cache_file_is_tmp="yes"
        html_cache_file=`mktemp "${TMPDIR:-/tmp}"/yelp-XXXXXXXX`
        yelp_cache -o "$html_cache_file" "$@"
    fi
    html_tmp_infile=`mktemp "${TMPDIR:-/tmp}"/yelp-XXXXXXXX`
    yelp_cache_in "$@" > "$html_tmp_infile"
    # Output HTML
    (
        echo '<xsl:stylesheet'
        echo ' xmlns:xsl="http://www.w3.org/1999/XSL/Transform"'
        echo ' xmlns:mal="http://projectmallard.org/1.0/"'
        echo ' xmlns:cache="http://projectmallard.org/cache/1.0/"'
        echo ' xmlns:exsl="http://exslt.org/common"'
        echo ' xmlns="http://www.w3.org/1999/xhtml"'
        echo ' exclude-result-prefixes="mal cache"'
        echo ' extension-element-prefixes="exsl"'
        echo ' version="1.0">'
        if [ "x$is_xhtml" = "x1" ]; then
            xsl='file://'`echo "$xsl_mal2xhtml" | urlencode`
        else
            xsl='file://'`echo "$xsl_mal2html" | urlencode`
        fi
        echo '<xsl:import href="'"$xsl"'"/>'
        echo '<xsl:include href="'"$xsl_mal_files"'"/>'
        echo '<xsl:output method="text"/>'
        yelp_html_xsl_common
        html_cache_url='file://'`echo "$html_cache_file" | urlencode`
        echo '<xsl:param name="mal.cache.file" select="'"'$html_cache_url'"'"/>'
        echo '<xsl:template match="/">'
        echo '<xsl:for-each select="cache:cache/mal:page | cache:cache/mal:stack">'
        echo '<xsl:variable name="href" select="@cache:href"/>'
        echo '<xsl:for-each select="document(@cache:href)">'
        echo '<xsl:for-each select="mal:page | mal:stack/mal:page">'
        echo '<xsl:call-template name="html.output"/>'
        echo '<xsl:call-template name="mal.files.copy">'
        echo ' <xsl:with-param name="href" select="substring-after($href, '\''file://'\'')"/>'
        echo '</xsl:call-template>'
        echo '</xsl:for-each>'
        echo '</xsl:for-each>'
        echo '</xsl:for-each>'
        echo '</xsl:template>'
        echo '</xsl:stylesheet>'
    ) | (cd "$html_out" && xsltproc $html_profile \
        --path "$html_paths" --xinclude \
        - "$html_tmp_infile") | sort | uniq | \
    while read line; do
        # Copy media from paths output by HTML transform
        line_src=$(echo "$line" | cut -d' ' -f1 | urldecode)
        line_dest="$html_out/"$(echo "$line" | cut -d' ' -f2)
        if [ ! -f "$line_src" -a "x$html_paths" != "x" ]; then
            line_src_rel=${line_src#"$(pwd)/"}
            if [ "x$line_src_rel" != "x$line_src" ]; then
                for path in $html_paths; do
                    if [ -f "$path/$line_src_rel" ]; then
                        line_src="$path/$line_src_rel"
                        break
                    fi
                done
            fi
        fi
        line_dest=`echo "$line_dest" | urldecode`
        if [ "$line_src" != "$line_dest" ]; then
            line_dir=`dirname "$line_dest"`
            mkdir_p "$line_dir"
            if [ -f "$line_src" -o "x$html_ignore_media" != "x1" ]; then
                cp "$line_src" "$line_dest"
            fi
        fi
    done
    # Copy JavaScript
    cp "${yelp_js_dir}/highlight.pack.js" "$html_out/$html_internal_datadir"
    # Clean up
    rm "$html_tmp_infile"
    if [ "x$html_cache_file_is_tmp" = "xyes" ]; then
        rm "$html_cache_file"
    fi
}

yelp_html () {
    while [ "$#" != "0" ]; do
        case "$1" in
            "-c")
                shift
                html_cache_file="$1"
                shift
                ;;
            "-o")
                shift
                html_out="$1"
                shift
                ;;
            "-x")
                shift
                html_custom="$1"
                shift
                ;;
            "-p")
                shift
                html_paths=$(yelp_paths_normalize "$1")
                shift
                ;;
            "-i")
                shift
                html_ignore_media="1"
                ;;
            "--profile")
                html_profile="--profile"
                shift
                ;;
            *)
                break
                ;;
        esac
    done
    if [ "x$html_out" = "x" ]; then
        html_out="."
    elif [ ! -d "$html_out" ]; then
        echo "Error: output must be a directory." 1>&2
        exit 1
    fi
    html_out=`(cd "$html_out" && pwd)`
    if [ "x$html_custom" != "x" ]; then
        html_custom_dir=`dirname "$html_custom"`
        html_custom_dir=`(cd "$html_custom_dir" && pwd)`
        html_custom="$html_custom_dir"/`basename "$html_custom"`
    fi
    if [ "$#" = "0" -o "x$1" = "x--help" -o "x$1" = "x-h" ]; then
        yelp_usage_html
        exit 1
    fi
    ext=`echo "$1" | sed -e 's/.*\.//'`
    if [ "x$ext" = "xxml" -o "x$ext" = "xdocbook" ]; then
        yelp_html_db2html "$@"
    else
        yelp_html_mal2html "$@"
    fi
}

yelp_epub () {
    while [ "$#" != "0" ]; do
        case "$1" in
            "-c")
                shift
                epub_cache_file="$1"
                shift
                ;;
            "-o")
                shift
                epub_out="$1"
                shift
                ;;
            "-x")
                shift
                html_custom="$1"
                shift
                ;;
            "-p")
                shift
                html_paths=$(yelp_paths_normalize "$1")
                shift
                ;;
            "-i")
                shift
                html_ignore_media="1"
                ;;
            *)
                break
                ;;
        esac
    done
    if [ "$#" = "0" -o "x$1" = "x--help" -o "x$1" = "x-h" ]; then
        yelp_usage_epub
        exit 1
    fi
    if [ "x$epub_cache_file" != "x" ]; then
        epub_cache_file=`(cd $(dirname "$epub_cache_file") && pwd)`/`basename "$epub_cache_file"`
    else
        epub_cache_file_is_tmp="yes"
        epub_cache_file=`mktemp "${TMPDIR:-/tmp}"/yelp-XXXXXXXX`
        yelp_cache -o "$epub_cache_file" "$@"
    fi
    html_cache_file="$epub_cache_file"
    epub_data_out=`mktemp -d "${TMPDIR:-/tmp}"/yelp-XXXXXXXX`
    html_out="$epub_data_out/OPS"
    mkdir "$html_out"
    mkdir "$html_out/yelp"
    html_internal_datadir="yelp/"
    html_internal_xsl="$xsl_mal_epub"
    yelp_html_mal2html "$@"

    epub_id=`uuidgen`
    epub_data=`(cd "$html_out" && ls yelp/*.*)`
    xsltproc \
        --path "$html_paths" \
        -o "$html_out/opf.opf" \
        --stringparam opf.id "$epub_id" \
        --stringparam opf.data "$epub_data" \
        "$xsl_mal_opf" "$epub_cache_file"
    xsltproc \
        --path "$html_paths" \
        -o "$html_out/ncx.ncx" \
        --stringparam ncx.id "$epub_id" \
        "$xsl_mal_ncx" "$epub_cache_file"

    echo "application/epub+zip" > "$epub_data_out/mimetype"

    mkdir "$epub_data_out/META-INF"
    (
        echo "<?xml version='1.0' encoding='UTF-8'?>"
        echo "<container version='1.0' xmlns='urn:oasis:names:tc:opendocument:xmlns:container'>"
        echo "<rootfiles>"
        echo "<rootfile full-path='OPS/opf.opf' media-type='application/oebps-package+xml'/>"
        echo "</rootfiles>"
        echo "</container>"
    ) > "$epub_data_out/META-INF/container.xml"

    if [ "x$epub_out" = "x" ]; then
        epub_out=`pwd`/index.epub
    else
        epub_out=`(cd $(dirname "$epub_out") && pwd)`/`basename "$epub_out"`
    fi
    (cd "$epub_data_out" && zip -q -r "$epub_out" mimetype META-INF OPS)

    if [ "x$epub_cache_file_is_tmp" = "xyes" ]; then
        rm "$epub_cache_file"
    fi
    rm -rf "$html_out"
}

cmd="$1"
shift
case "x$cmd" in
    "xcache")
        yelp_cache "$@"
        ;;
    "xepub")
        is_xhtml=1
        yelp_epub "$@"
        ;;
    "xhtml")
        is_xhtml=0
        yelp_html "$@"
        ;;
    "xxhtml")
        is_xhtml=1
        yelp_html "$@"
        ;;
    *)
        yelp_usage
        ;;
esac
