#!/usr/bin/python
# Build a bibliography for an xml2rfc input file
#
# Eric Rescorla <ekr@rtfm.com>
#
# Copyright (c) 2008, RTFM, INC.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#     * Redistributions of source code must retain the above copyright
#       notice, this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above copyright
#       notice, this list of conditions and the following disclaimer in the
#       documentation and/or other materials provided with the distribution.
#     * Neither the name of the RTFM, INC. nor the
#       names of its contributors may be used to endorse or promote products
#       derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY RTFM, INC. ``AS IS'' AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL RTFM, INC. BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import sys
import getopt
import urllib2
from xml.sax import make_parser
from xml.sax import ContentHandler, handler
from os.path import exists, dirname, abspath, isdir
from os import mkdir


# Constants
REFDIR="references"
REF_RFC_URI_PREFIX="http://xml.resource.org/public/rfc/bibxml/reference.RFC."
REF_ID_URI_PREFIX="http://xml.resource.org/public/rfc/bibxml3/reference."


def usage():
    sys.stderr.write("usage: bibxml2rfc [-inv] filename\n\
    -i fake entries for IETF documents\n\
    -n don't try to download new entries (operate from cache)\n\
    -v verbose\n")
    
    sys.exit(1)


def get_reference(name,cachefile):
    report("Getting reference "+name+" (cachefile="+cachefile+")")

    cached = None
    # Look in the cache
    report("Checking cache")
    if exists(cachefile):
        report("Already in cache")
        fh = open(cachefile,'r')
        cached=fh.read()

    if name.startswith("I-D."):
        # Internet Draft
        if (cached != None) and (dont_refresh_entries):
            return cached
        
        # There may be new versions so need to check the net
        uri=REF_ID_URI_PREFIX + name + ".xml"
    elif name.startswith("RFC"):
        # RFC
        # RFCs never change
        if cached != None:
            return cached
        uri=REF_RFC_URI_PREFIX + name[3:] + ".xml"
    else:
        # Something unknown. Don't know how to fetch
        return cached

    try:
        report("Trying to fetch uri "+uri)
        fh = urllib2.urlopen(uri)
        res = fh.read()
        fh.close()
    except Exception:
        warning("Could not fetch referene for "+name+" (url="+uri+")")
        if cached != None:
            report("Using cached value")
            return cached
        
        if fake_ietf_entries == True:
            report("Faking entry for "+name)
            res = '<reference anchor="'+name+'">\n' + '<front><title>Fake Entry</title>\n'+'<author initials="F." surname="Author" fullname="Fake Author"/>\n'+ '<date year="2000"/>\n'+ '</front>\n'+ '<seriesInfo name="Fake" value="Series"/>\n'+ '</reference>\n'
            return res
        else:
            return None

    # OK, we have the file. Now open the cachefile and store it there
    report("Successfully fetched "+name)
    fh = open(cachefile, 'w')
    fh.write(res)
    fh.close()
    return res

def warning(a):
    sys.stderr.write(a+"\n")

def report(a):
    if verbose == True:
        sys.stdout.write(a+"\n")
def opt_isset(opts,flag):
    for opt in opts:
        if opt[0] == flag:
            return True
    
# Control parameters
verbose = False
fake_ietf_entries = True
dont_refresh_entries = False

# Read the command line arguments
getopt_out=getopt.getopt(sys.argv[1:],"vin")
options=getopt_out[0]

if opt_isset(options,'-v'):
    verbose=True
if opt_isset(options,'-i'):
    fake_ietf_entries=True
if opt_isset(options,'-n'):
    dont_refresh_entries=True

arguments=getopt_out[1]
if len(arguments)!=1:
    usage()
filename=arguments[0]
if exists(filename)!=True:
    sys.stderr.write("File "+filename+" does not exist\n")
    sys.exit(1)
filedir=dirname(abspath(filename))

# If the references directory does not exist, make it
refdir = filedir + '/' + REFDIR
if isdir(refdir)==False:
    mkdir(refdir)

# Open the files to write to
f_normative = open(filename + "-normative","w")
f_informative = open(filename + "-informative","w")

reference_out={};
reference_body={};


# Find all the references
class XrefHandler(ContentHandler):
    def startElement(self, name, attrs):
        if name.lower() != "xref":
            return

        try:
            target = attrs.getValue("target")
        except KeyError:
            return

        report("processing xref for "+target)
        
        if target.find("/") != -1:
            warning("Illegal character '/' in target '"+target+"'")
            return

        # Figure out what kind of reference it is
        try:
            norm = attrs.getValue("norm").lower()
            if norm == "true":
                normative=f_normative
            elif norm == "false":
                normative=f_informative
            else:
                sys.stderr.write("Unknown 'norm' value '"+norm+"' for target '"+target+"'. Assuming informative.\n")
                normative=f_informative
        except KeyError:
            normative=f_informative
            
        cachefile = refdir + "/" + target + ".xml"
            
        ref_body = get_reference(target, cachefile)
        if ref_body != None:
            """
            Add this reference if it's not already listed
            If it is already listed but as informative and defined
            here as normative, then override it"""
            
            if reference_out.has_key(target)==False:
                reference_out[target]=normative
                reference_body[target]=ref_body
            else:
                if reference_out[target]==f_informative and normative==True:
                    reference_out[target]=f_normative


# Parse the document
parser=make_parser()
parser.setContentHandler(XrefHandler())
parser.setFeature(handler.feature_external_ges,False)
parser.parse(filename)

# Now write out the reference sections
refs = reference_out.keys()
for key in sorted(refs):
    report("Including reference for "+key)
    reference_out[key].write("\n\n")
    reference_out[key].write(reference_body[key])

# Success
sys.exit(0)
