import os, string, cStringIO

from Ft.Lib import Uri
from Ft.Xml import XInclude, Domlette, cDomlette
from Ft.Xml import ReaderException, XIncludeException

READER = Domlette.NonvalidatingReader

BASE_URI = Uri.OsPathToUri(os.path.abspath(__file__), attemptAbsolute=True)

# Determine platform line seperator for textual inclusions
f = open(os.path.join(os.path.dirname(__file__), 'data.xml'), 'rb')
line = f.readline()
f.close()
if line.endswith(os.linesep):
    LINESEP = os.linesep.replace('\r', '&#13;')
else:
    # Assume UNIX line-ending
    LINESEP = '\n'
del f, line

def Test(tester):
    tester.startGroup("Source from cDomlette")
    test_xinclude(tester)
    test_xinclude_xptr(tester)
    test_lowlevel_xinclude_xptr(tester)
    tester.groupDone()
    return

def doTest(tester, title, src, expected):
    tester.startTest(title)
    doc = READER.parseString(src, BASE_URI)
    st = cStringIO.StringIO()
    Domlette.Print(doc, stream=st)
    # FIXME: compare infosets, not strings
    tester.compare(expected, st.getvalue())
    tester.testDone()
    return

def doExceptionTest(tester, title, expected_exc_class, expected_exc_attrs,
                    src_str=None, src_filename=None):
    tester.startTest(title)
    if src_filename is not None:
        uri = Uri.Absolutize(src_filename, BASE_URI)
        tester.testException(READER.parseUri, (uri,),
                             expected_exc_class, expected_exc_attrs)
    elif src_str is not None:
        tester.testException(READER.parseString, (src_str, BASE_URI),
                             expected_exc_class, expected_exc_attrs)
    else:
        tester.exception("No source to attempt to parse!")
    tester.testDone()
    return


def test_xinclude(tester):
    tester.startGroup("XInclude only")

    tester.startGroup('XInclude spec examples')
    doTest(tester, 'C.1 Basic Inclusion', SPEC_SRC_1, SPEC_EXPECTED_1)
    doTest(tester, 'C.2 Textual Inclusion', SPEC_SRC_2, SPEC_EXPECTED_2)
    doTest(tester, 'C.3 Textual Inclusion of XML', SPEC_SRC_3, SPEC_EXPECTED_3)
    tester.groupDone()

    tester.startGroup('Misc tests')
    doTest(tester, 'simple XInclude', SRC_1, expected_1)
    doTest(tester, 'two-level XInclude', SRC_2, expected_2)
    doTest(tester, 'textual inclusion of XML', SRC_3, expected_3)
    tester.groupDone()

    tester.startGroup('Misc exception tests')
    doExceptionTest(tester, 'recursive XInclude', ReaderException,
                    {'errorCode': ReaderException.RECURSIVE_PARSE_ERROR},
                    src_filename='recursive-xinclude.xml')
    doExceptionTest(tester, 'empty href', XIncludeException,
                    {'errorCode': XIncludeException.MISSING_HREF},
                    src_str=EXC_SRC_1)
    doExceptionTest(tester, 'missing href', XIncludeException,
                    {'errorCode': XIncludeException.MISSING_HREF},
                    src_str=EXC_SRC_2)
    doExceptionTest(tester, 'invalid value for parse attribute', XIncludeException,
                    {'errorCode': XIncludeException.INVALID_PARSE_ATTR},
                    src_str=EXC_SRC_3)
    tester.groupDone()

    tester.groupDone()
    return


def test_xinclude_xptr(tester):
    tester.startGroup("XInclude with XPointer")

    tester.startGroup('XInclude spec examples')
    tester.startTest('C.4 Fragment Inclusion')
    tester.warning('Not tested; requires ID-type attribute support')
    tester.testDone()
    #doTest(tester, 'C.4 Fragment Inclusion', SPEC_SRC_4, SPEC_EXPECTED_4)
    tester.startTest('C.5 Range Inclusion')
    tester.warning('Not tested; requires XPointer range support')
    tester.testDone()
    #doTest(tester, 'C.5 Range Inclusion', SPEC_SRC_5, SPEC_EXPECTED_5)
    doTest(tester, 'C.6 Fallback', SPEC_SRC_6, SPEC_EXPECTED_6)
    tester.groupDone()

    tester.startGroup('Misc tests')
    doTest(tester, '/ADDRBOOK/ENTRY part 1', SRC_4, expected_4)
    doTest(tester, '/ADDRBOOK/ENTRY part 2', SRC_5, expected_5)
    doTest(tester, '/ADDRBOOK', SRC_6, expected_6)
    doTest(tester, '/ADDRBOOK/ENTRY/NAME', SRC_7, expected_7)
    doTest(tester, '/ADDRBOOK/ENTRY[2]', SRC_8, expected_8)
    doTest(tester, "/ADDRBOOK/ENTRY[@ID='en']", SRC_9, expected_9)
    doTest(tester, "/messages/msg[@xml:lang='en']", SRC_10, expected_10)
    doTest(tester, '/x:ADDRBOOK/x:ENTRY with x=http://spam.com', SRC_11, expected_11)
    tester.groupDone()

    tester.groupDone()
    return


def test_lowlevel_xinclude_xptr(tester):
    tester.startGroup("Low-level XInclude with XPointer")
    ELEMENT_MATCH = cDomlette.ELEMENT_MATCH
    ELEMENT_COUNT = cDomlette.ELEMENT_COUNT
    ATTRIBUTE_MATCH = cDomlette.ATTRIBUTE_MATCH
    START = cDomlette.INITIAL_STATE
    BASE = cDomlette.XPTR_START_STATE

    FRAG = "#xpointer(/spam)"
    EXPECTED = [(START, BASE, BASE+1, BASE+2,
                 [(ELEMENT_MATCH, None, u'spam')])]
    tester.startTest(FRAG)
    spec = cDomlette.ProcessFragment(FRAG)
    tester.compare(EXPECTED, spec.states)
    tester.testDone()

    FRAG = "#xpointer(/spam/eggs)"
    EXPECTED = [(START, BASE, BASE+1, BASE+2,
                 [(ELEMENT_MATCH, None, u'spam')]),
                (BASE, BASE+3, BASE+4, BASE+5,
                 [(ELEMENT_MATCH, None, u'eggs')])]
    tester.startTest(FRAG)
    spec = cDomlette.ProcessFragment(FRAG)
    tester.compare(EXPECTED, spec.states)
    tester.testDone()

    FRAG = "#xpointer(/spam/eggs/juice)"
    EXPECTED = [(START, BASE, BASE+1, BASE+2,
                 [(ELEMENT_MATCH, None, u'spam')]),
                (BASE, BASE+3, BASE+4, BASE+5,
                 [(ELEMENT_MATCH, None, u'eggs')]),
                (BASE+3, BASE+6, BASE+7, BASE+8,
                 [(ELEMENT_MATCH, None, u'juice')])]
    tester.startTest(FRAG)
    spec = cDomlette.ProcessFragment(FRAG)
    tester.compare(EXPECTED, spec.states)
    tester.testDone()

    FRAG = "#xpointer(spam)"
    EXPECTED = [(START, BASE, BASE+1, BASE+2,
                 [(ELEMENT_MATCH, None, u'spam')])]
    tester.startTest(FRAG)
    spec = cDomlette.ProcessFragment(FRAG)
    tester.compare(EXPECTED, spec.states)
    tester.testDone()

    FRAG = "#xpointer(spam/eggs)"
    EXPECTED = [(START, BASE, BASE+1, BASE+2,
                 [(ELEMENT_MATCH, None, u'spam')]),
                (BASE, BASE+3, BASE+4, BASE+5,
                 [(ELEMENT_MATCH, None, u'eggs')])]
    tester.startTest(FRAG)
    spec = cDomlette.ProcessFragment(FRAG)
    tester.compare(EXPECTED, spec.states)
    tester.testDone()

    FRAG = "#xpointer(spam/eggs/juice)"
    EXPECTED = [(START, BASE, BASE+1, BASE+2, [(ELEMENT_MATCH, None, u'spam')]),
                (BASE, BASE+3, BASE+4, BASE+5, [(ELEMENT_MATCH, None, u'eggs')]),
                (BASE+3, BASE+6, BASE+7, BASE+8, [(ELEMENT_MATCH, None, u'juice')])]
    tester.startTest(FRAG)
    spec = cDomlette.ProcessFragment(FRAG)
    tester.compare(EXPECTED, spec.states)
    tester.testDone()

    FRAG = "#xpointer(/spam/eggs[1])"
    EXPECTED = [(START, BASE, BASE+1, BASE+2, [(ELEMENT_MATCH, None, u'spam')]),
                (BASE, BASE+3, BASE+4, BASE+5, [(ELEMENT_MATCH, None, u'eggs'), (ELEMENT_COUNT, 1, [1])])]
    tester.startTest(FRAG)
    spec = cDomlette.ProcessFragment(FRAG)
    tester.compare(EXPECTED, spec.states)
    tester.testDone()

    FRAG = "#xpointer(/spam[1]/eggs)"
    EXPECTED = [(START, BASE, BASE+1, BASE+2, [(ELEMENT_MATCH, None, u'spam'), (ELEMENT_COUNT, 1, [1])]),
                (BASE, BASE+3, BASE+4, BASE+5, [(ELEMENT_MATCH, None, u'eggs')])]
    tester.startTest(FRAG)
    spec = cDomlette.ProcessFragment(FRAG)
    tester.compare(EXPECTED, spec.states)
    tester.testDone()

    FRAG = "#xpointer(/spam[1]/eggs[1])"
    EXPECTED = [(START, BASE, BASE+1, BASE+2, [(ELEMENT_MATCH, None, u'spam'), (ELEMENT_COUNT, 1, [1])]),
                (BASE, BASE+3, BASE+4, BASE+5, [(ELEMENT_MATCH, None, u'eggs'), (ELEMENT_COUNT, 1, [1])])]
    tester.startTest(FRAG)
    spec = cDomlette.ProcessFragment(FRAG)
    tester.compare(EXPECTED, spec.states)
    tester.testDone()

    FRAG = "#xpointer(/spam/eggs[@a='b'])"
    EXPECTED = [(START, BASE, BASE+1, BASE+2, [(ELEMENT_MATCH, None, u'spam')]),
                (BASE, BASE+3, BASE+4, BASE+5, [(ELEMENT_MATCH, None, u'eggs'), (ATTRIBUTE_MATCH, None, u'a', u'b')])]
    tester.startTest(FRAG)
    spec = cDomlette.ProcessFragment(FRAG)
    tester.compare(EXPECTED, spec.states)
    tester.testDone()

    FRAG = "#xpointer(/spam[@a='b']/eggs)"
    EXPECTED = [(START, BASE, BASE+1, BASE+2, [(ELEMENT_MATCH, None, u'spam'), (ATTRIBUTE_MATCH, None, u'a', u'b')]),
                (BASE, BASE+3, BASE+4, BASE+5, [(ELEMENT_MATCH, None, u'eggs')])]
    tester.startTest(FRAG)
    spec = cDomlette.ProcessFragment(FRAG)
    tester.compare(EXPECTED, spec.states)
    tester.testDone()

    FRAG = "#xpointer(/spam[@a='b']/eggs[@a='b'])"
    EXPECTED = [(START, BASE, BASE+1, BASE+2, [(ELEMENT_MATCH, None, u'spam'), (ATTRIBUTE_MATCH, None, u'a', u'b')]),
                (BASE, BASE+3, BASE+4, BASE+5, [(ELEMENT_MATCH, None, u'eggs'), (ATTRIBUTE_MATCH, None, u'a', u'b')])]
    tester.startTest(FRAG)
    spec = cDomlette.ProcessFragment(FRAG)
    tester.compare(EXPECTED, spec.states)
    tester.testDone()

    FRAG = "#xmlns(ns=http://spam.com) xpointer(/spam/eggs[@ns:a='b'])"
    EXPECTED = [(START, BASE, BASE+1, BASE+2, [(ELEMENT_MATCH, None, u'spam')]),
                (BASE, BASE+3, BASE+4, BASE+5, [(ELEMENT_MATCH, None, u'eggs'), (ATTRIBUTE_MATCH, u'http://spam.com', u'a', u'b')])]
    tester.startTest(FRAG)
    spec = cDomlette.ProcessFragment(FRAG)
    tester.compare(EXPECTED, spec.states)
    tester.testDone()

    tester.groupDone()
    return

SPEC_SRC_1 = """<?xml version='1.0'?>
<document xmlns:xi="http://www.w3.org/2001/XInclude">
  <p>120 Mz is adequate for an average home user.</p>
  <xi:include href="disclaimer.xml"/>
</document>"""

SPEC_EXPECTED_1 = """<?xml version="1.0" encoding="UTF-8"?>
<document xmlns:xi="http://www.w3.org/2001/XInclude">
  <p>120 Mz is adequate for an average home user.</p>
  <disclaimer xml:base="http://www.example.org/disclaimer.xml">
  <p>The opinions represented herein represent those of the individual
  and should not be interpreted as official policy endorsed by this
  organization.</p>
</disclaimer>
</document>"""

SPEC_SRC_2 = """<?xml version='1.0'?>
<document xmlns:xi="http://www.w3.org/2001/XInclude">
  <p>This document has been accessed
  <xi:include href="count.txt" parse="text"/> times.</p>
</document>"""

SPEC_EXPECTED_2 = """<?xml version="1.0" encoding="UTF-8"?>
<document xmlns:xi="http://www.w3.org/2001/XInclude">
  <p>This document has been accessed
  324387 times.</p>
</document>"""

SPEC_SRC_3 = """<?xml version='1.0'?>
<document xmlns:xi="http://www.w3.org/2001/XInclude">
  <p>The following is the source of the "data.xml" resource:</p>
  <example><xi:include href="data.xml" parse="text"/></example>
</document>"""

SPEC_EXPECTED_3 = """<?xml version="1.0" encoding="UTF-8"?>
<document xmlns:xi="http://www.w3.org/2001/XInclude">
  <p>The following is the source of the "data.xml" resource:</p>
  <example>&lt;?xml version='1.0'?&gt;%(linesep)s&lt;data&gt;%(linesep)s  &lt;item&gt;&lt;![CDATA[Brooks &amp; Shields]]&gt;&lt;/item&gt;%(linesep)s&lt;/data&gt;</example>
</document>""" % {'linesep' : LINESEP}

SPEC_SRC_4 = """<?xml version='1.0'?>
<price-quote xmlns:xi="http://www.w3.org/2001/XInclude">
  <prepared-for>Joe Smith</prepared-for>
  <good-through>20040930</good-through>
  <xi:include href="price-list.xml" xpointer="w002-description"/>
  <volume>40</volume>
  <xi:include href="price-list.xml" xpointer="element(w002-prices/2)"/>
</price-quote>"""

SPEC_EXPECTED_4 = """<?xml version="1.0" encoding="UTF-8"?>
<price-quote xmlns:xi="http://www.w3.org/2001/XInclude">
  <prepared-for>Joe Smith</prepared-for>
  <good-through>20040930</good-through>
  <description id="w002-description" xml:lang="en-us"
               xml:base="http://www.example.com/price-list.xml">
    <p>Super-sized widget with bells <i>and</i> whistles.</p>
  </description>
  <volume>40</volume>
  <price currency="USD" volume="10+" xml:lang="en-us"
         xml:base="http://www.example.com/price-list.xml">54.95</price>
</price-quote>"""

SPEC_SRC_5 = """<?xml version='1.0'?>
<document>
  <p>The relevant excerpt is:</p>
  <quotation>
    <include xmlns="http://www.w3.org/2001/XInclude"
       href="source.xml" xpointer="xpointer(string-range(chapter/p[1],'Sentence 2')/
             range-to(string-range(/chapter/p[2]/i,'3.',1,2)))"/>
  </quotation>
</document>"""

SPEC_EXPECTED_5 = """<?xml version="1.0" encoding="UTF-8"?>
<?xml version='1.0'?>
<document>
  <p>The relevant excerpt is:</p>
  <quotation>
    <p xml:base="http://www.example.com/source.xml">Sentence 2.</p>
  <p xml:base="http://www.example.com/source.xml"><i>Sentence 3.</i></p>
  </quotation>
</document>"""

SPEC_SRC_6 = """<?xml version='1.0'?>
<div>
  <xi:include href="example.txt" parse="text" xmlns:xi="http://www.w3.org/2001/XInclude">
    <xi:fallback><xi:include href="fallback-example.txt" parse="text">
        <xi:fallback><a href="mailto:bob@example.org">Report error</a></xi:fallback>
      </xi:include></xi:fallback>
  </xi:include>
</div>"""

SPEC_EXPECTED_6 = """<?xml version="1.0" encoding="UTF-8"?>
<div>
  <a href="mailto:bob@example.org">Report error</a>
</div>"""

SRC_1="""<?xml version='1.0'?><x xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="include1.xml"/>
</x>"""

expected_1 = """<?xml version="1.0" encoding="UTF-8"?>
<x xmlns:xi="http://www.w3.org/2001/XInclude">
<foo/>
</x>"""

SRC_2="""<?xml version='1.0'?><x xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="include2.xml"/>
</x>"""

expected_2="""<?xml version="1.0" encoding="UTF-8"?>
<x xmlns:xi="http://www.w3.org/2001/XInclude">
<foo>
  <foo/>
</foo>
</x>"""

SRC_3="""<?xml version='1.0'?><x xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="include2.xml" parse='text'/>
</x>"""

expected_3="""<?xml version="1.0" encoding="UTF-8"?>
<x xmlns:xi="http://www.w3.org/2001/XInclude">
&lt;?xml version='1.0' encoding='utf-8'?&gt;%(linesep)s&lt;foo xmlns:xi="http://www.w3.org/2001/XInclude"&gt;%(linesep)s  &lt;xi:include href="include1.xml"/&gt;%(linesep)s&lt;/foo&gt;
</x>""" % {'linesep' : LINESEP}

SRC_4="""<?xml version='1.0'?><x xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="include3.xml#xpointer(/ADDRBOOK/ENTRY)"/>
</x>"""

expected_4 = """<?xml version="1.0" encoding="UTF-8"?>
<x xmlns:xi="http://www.w3.org/2001/XInclude">
<ENTRY ID="pa">
    <NAME>Pieter Aaron</NAME>
    <EMAIL>pieter.aaron@inter.net</EMAIL>
  </ENTRY>
</x>"""

SRC_5="""<?xml version='1.0'?><x xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="include4.xml#xpointer(/ADDRBOOK/ENTRY)"/>
</x>"""

expected_5 = """<?xml version="1.0" encoding="UTF-8"?>
<x xmlns:xi="http://www.w3.org/2001/XInclude">
<ENTRY ID="pa">
    <NAME>Pieter Aaron</NAME>
    <EMAIL>pieter.aaron@inter.net</EMAIL>
  </ENTRY><ENTRY ID="en">
    <NAME>Emeka Ndubuisi</NAME>
    <EMAIL>endubuisi@spamtron.com</EMAIL>
  </ENTRY><ENTRY ID="vz">
    <NAME>Vasia Zhugenev</NAME>
    <EMAIL>vxz@gog.ru</EMAIL>
  </ENTRY>
</x>"""

SRC_6="""<?xml version='1.0'?><x xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="include3.xml#xpointer(/ADDRBOOK)"/>
</x>"""

expected_6 = """<?xml version="1.0" encoding="UTF-8"?>
<x xmlns:xi="http://www.w3.org/2001/XInclude">
<ADDRBOOK>
  <ENTRY ID="pa">
    <NAME>Pieter Aaron</NAME>
    <EMAIL>pieter.aaron@inter.net</EMAIL>
  </ENTRY>
</ADDRBOOK>
</x>"""

SRC_7="""<?xml version='1.0'?><x xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="include3.xml#xpointer(/ADDRBOOK/ENTRY/NAME)"/>
</x>"""

expected_7 = """<?xml version="1.0" encoding="UTF-8"?>
<x xmlns:xi="http://www.w3.org/2001/XInclude">
<NAME>Pieter Aaron</NAME>
</x>"""

SRC_8="""<?xml version='1.0'?><x xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="include4.xml#xpointer(/ADDRBOOK/ENTRY[2])"/>
</x>"""

expected_8 = """<?xml version="1.0" encoding="UTF-8"?>
<x xmlns:xi="http://www.w3.org/2001/XInclude">
<ENTRY ID="en">
    <NAME>Emeka Ndubuisi</NAME>
    <EMAIL>endubuisi@spamtron.com</EMAIL>
  </ENTRY>
</x>"""

SRC_9="""<?xml version='1.0'?><x xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="include4.xml#xpointer(/ADDRBOOK/ENTRY[@ID='en'])"/>
</x>"""

expected_9 = """<?xml version="1.0" encoding="UTF-8"?>
<x xmlns:xi="http://www.w3.org/2001/XInclude">
<ENTRY ID="en">
    <NAME>Emeka Ndubuisi</NAME>
    <EMAIL>endubuisi@spamtron.com</EMAIL>
  </ENTRY>
</x>"""

SRC_10="""<?xml version='1.0'?><x xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="include6.xml#xpointer(/messages/msg[@xml:lang='en'])"/>
</x>"""

expected_10 = """<?xml version="1.0" encoding="UTF-8"?>
<x xmlns:xi="http://www.w3.org/2001/XInclude">
<msg xml:lang="en">Hello</msg>
</x>"""

SRC_11="""<?xml version='1.0'?><x xmlns:xi="http://www.w3.org/2001/XInclude">
  <xi:include href="include7.xml#xmlns(x=http://spam.com) xpointer(/x:ADDRBOOK/x:ENTRY)"/>
</x>"""

expected_11 = """<?xml version="1.0" encoding="UTF-8"?>
<x xmlns:xi="http://www.w3.org/2001/XInclude">
  <ENTRY xmlns="http://spam.com" ID="pa">
    <NAME>Pieter Aaron</NAME>
    <EMAIL>pieter.aaron@inter.net</EMAIL>
  </ENTRY>
</x>"""

# NOTE: technically, this does not have to be an error;
# it is up to the processor whether it handles it or not
EXC_SRC_1 = """<?xml version="1.0" encoding="utf-8"?>
<wrapper>
<xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="" parse="xml"/>
</wrapper>"""

# NOTE: technically, this does not have to be an error;
# it is up to the processor whether it handles it or not
EXC_SRC_2 = """<?xml version="1.0" encoding="utf-8"?>
<wrapper>
<xi:include xmlns:xi="http://www.w3.org/2001/XInclude" parse="xml"/>
</wrapper>"""

# Fatal error according to the spec
EXC_SRC_3 = """<?xml version="1.0" encoding="utf-8"?>
<wrapper>
<xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="include1.xml" parse="bogus"/>
</wrapper>"""


if __name__ == '__main__':
    from Ft.Lib.TestSuite import Tester
    tester = Tester.Tester()
    Test(tester)

