text:
@testset "Events/Entity Declarations" begin
    evaluate(s) = collect(E.events(L.State(IOBuffer(s))))

    E = Events
    L = Events.Lexical

    function ExternalGeneralData(name, public_id, system_id, notation, location)
        return E.EntityDeclarationExternalGeneralData(name, E.ExternalIdentifier(public_id, system_id, location),
                                                      notation, location)
    end

    function ExternalGeneralText(name, location)
        return E.EntityDeclarationExternalGeneralText(name, nothing, location)
    end

    function ExternalGeneralText(name, public_id, system_id, location)
        return E.EntityDeclarationExternalGeneralText(name, E.ExternalIdentifier(public_id, system_id, location), location)
    end

    function ExternalParameter(name, location)
        return E.EntityDeclarationExternalParameter(name, nothing, location)
    end

    function ExternalParameter(name, public_id, system_id, location)
        return E.EntityDeclarationExternalParameter(name, E.ExternalIdentifier(public_id, system_id, location), location)
    end

    InternalGeneral   = E.EntityDeclarationInternalGeneral
    InternalParameter = E.EntityDeclarationInternalParameter

    DC = E.DataContent
    ME = E.MarkupError

    @testset "Events/Entity Declarations, Internal General (Positive)" begin
        # Basic tests ... empty entity value.
        #
        @test evaluate("<!ENTITY a \"\">")   == [ InternalGeneral("a", "", L.Location("a buffer", -1)) ]
        @test evaluate("<!ENTITY abc \"\">") == [ InternalGeneral("abc", "", L.Location("a buffer", -1)) ]
        @test evaluate("<!ENTITY é \"\">")   == [ InternalGeneral("é", "", L.Location("a buffer", -1)) ]
        @test evaluate("<!ENTITY aé \"\">")  == [ InternalGeneral("aé", "", L.Location("a buffer", -1)) ]
        @test evaluate("<!ENTITY éb \"\">")  == [ InternalGeneral("éb", "", L.Location("a buffer", -1)) ]
        @test evaluate("<!ENTITY aéb \"\">") == [ InternalGeneral("aéb", "", L.Location("a buffer", -1)) ]
        @test evaluate("<!ENTITY\na\n\"\">") == [ InternalGeneral("a", "", L.Location("a buffer", -1)) ]
        @test evaluate("<!ENTITY\na\t\"\">") == [ InternalGeneral("a", "", L.Location("a buffer", -1)) ]

        # Basic tests ... non-empty entity value.
        #
        @test evaluate("<!ENTITY e \"a\">")       == [ InternalGeneral("e", "a", L.Location("a buffer", -1)) ]
        @test evaluate("<!ENTITY e \"abc\">")     == [ InternalGeneral("e", "abc", L.Location("a buffer", -1)) ]
        @test evaluate("<!ENTITY e \"é\">")       == [ InternalGeneral("e", "é", L.Location("a buffer", -1)) ]
        @test evaluate("<!ENTITY e \"aé\">")      == [ InternalGeneral("e", "aé", L.Location("a buffer", -1)) ]
        @test evaluate("<!ENTITY e \"éb\">")      == [ InternalGeneral("e", "éb", L.Location("a buffer", -1)) ]
        @test evaluate("<!ENTITY e \"aéb\">")     == [ InternalGeneral("e", "aéb", L.Location("a buffer", -1)) ]
        @test evaluate("<!ENTITY e \"\na\n\">")   == [ InternalGeneral("e", "\na\n", L.Location("a buffer", -1)) ]
        @test evaluate("<!ENTITY e \"\na\t\">")   == [ InternalGeneral("e", "\na\t", L.Location("a buffer", -1)) ]
        @test evaluate("<!ENTITY e \"&other;\">") == [ InternalGeneral("e", "&other;", L.Location("a buffer", -1)) ]

        # This is from the XML specification, § 4.2.1 ...
        #
        @test (evaluate("<!ENTITY Pub-Status \"This is a pre-release of the\nspecification.\">")
               == [ InternalGeneral("Pub-Status", "This is a pre-release of the\nspecification.", L.Location("a buffer", -1)) ])

        # This is from the XML specification, § 4.4.5 ...
        #
        @test (evaluate("<!ENTITY WhatHeSaid \"He said %YN;\" >")
               == [ InternalGeneral("WhatHeSaid", "He said %YN;", L.Location("a buffer", -1)) ])
    end

    @testset "Events/Entity Declarations, Internal General (Negative ... invalid or absent entity name.)" begin
        @test (evaluate("<!ENTITY")
               == [ ME("ERROR: White space is required following the 'ENTITY' keyword.", L.Location("a buffer", -1)),
                    ME("ERROR: Expecting an entity name.", L.Location("a buffer", -1)) ])

        events = evaluate("<!ENTITY>")
        @test length(events) == 3
        @test (events == [ ME("ERROR: White space is required following the 'ENTITY' keyword.", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting an entity name.", L.Location("a buffer", -1)),
                           DC(">", false, L.Location("a buffer", -1)) ])

        events = evaluate("<!ENTITY >")
        @test length(events) == 2
        @test (events == [ ME("ERROR: Expecting an entity name.", L.Location("a buffer", -1)),
                           DC(">", false, L.Location("a buffer", -1)) ])

        # This one is a little weird: we're picking up "PUBLIC" as the entity name. I'll have to figure out later if
        # that's allowed or not, but there doesn't appear to be anything in the specification that forbids it.
        #
        events = evaluate("<!ENTITY PUBLIC")
        @test length(events) == 4
        @test (events == [ ME("ERROR: White space is required between the entity name and the entity value.",
                              L.Location("a buffer", -1)),
                           ME("ERROR: Expecting a quoted string.", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting '>' to end an entity declaration.", L.Location("a buffer", -1)),
                           ExternalGeneralText("PUBLIC", L.Location("a buffer", -1)) ])

        events = evaluate("<!ENTITY \"\"")
        @test length(events) == 3
        @test (events == [ ME("ERROR: Expecting an entity name.", L.Location("a buffer", -1)),
                           DC("\"", false, L.Location("a buffer", -1)),
                           DC("\"", false, L.Location("a buffer", -1)) ])

        events = evaluate("<!ENTITY ''")
        @test length(events) == 3
        @test (events == [ ME("ERROR: Expecting an entity name.", L.Location("a buffer", -1)),
                           DC("'", false, L.Location("a buffer", -1)),
                           DC("'", false, L.Location("a buffer", -1)) ])

        events = evaluate("<!ENTITY <")
        @test length(events) == 2
        @test (events == [ ME("ERROR: Expecting an entity name.", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting an element name.", L.Location("a buffer", -1)) ])
    end

    @testset "Events/Entity Declarations, Internal General (Negative ... missing entity value.)" begin
        events = evaluate("<!ENTITY e")
        @test length(events) == 4
        @test (events == [ ME("ERROR: White space is required between the entity name and the entity value.",
                              L.Location("a buffer", -1)),
                           ME("ERROR: Expecting a quoted string.", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting '>' to end an entity declaration.", L.Location("a buffer", -1)),
                           ExternalGeneralText("e", L.Location("a buffer", -1)) ])

        events = evaluate("<!ENTITY e ")
        @test length(events) == 3
        @test (events == [ ME("ERROR: Expecting a quoted string.", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting '>' to end an entity declaration.", L.Location("a buffer", -1)),
                           ExternalGeneralText("e", L.Location("a buffer", -1)) ])

        events = evaluate("<!ENTITY e <")
        @test length(events) == 4
        @test (events == [ ME("ERROR: Expecting a quoted string.", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting '>' to end an entity declaration.", L.Location("a buffer", -1)),
                           ExternalGeneralText("e", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting an element name.", L.Location("a buffer", -1)) ])
    end

    @testset "Events/Entity Declarations, Internal General (Negative ... missing TAGC.)" begin
        events = evaluate("<!ENTITY e \"value\"")
        @test length(events) == 2
        @test (events == [ ME("ERROR: Expecting '>' to end an entity declaration.", L.Location("a buffer", -1)),
                           InternalGeneral("e", "value", L.Location("a buffer", -1)) ])

        events = evaluate("<!ENTITY e \"value\" ")
        @test length(events) == 2
        @test (events == [ ME("ERROR: Expecting '>' to end an entity declaration.", L.Location("a buffer", -1)),
                           InternalGeneral("e", "value", L.Location("a buffer", -1)) ])

        events = evaluate("<!ENTITY e \"value\" <")
        @test length(events) == 3
        @test (events == [ ME("ERROR: Expecting '>' to end an entity declaration.", L.Location("a buffer", -1)),
                           InternalGeneral("e", "value", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting an element name.", L.Location("a buffer", -1)) ])
    end

    @testset "Events/Entity Declarations, Internal Parameter (Positive)" begin
        # Basic tests ... empty entity value.
        #
        @test evaluate("<!ENTITY % a \"\">")    == [ InternalParameter("a", "", L.Location("a buffer", -1)) ]
        @test evaluate("<!ENTITY % abc \"\">")  == [ InternalParameter("abc", "", L.Location("a buffer", -1)) ]
        @test evaluate("<!ENTITY % é \"\">")    == [ InternalParameter("é", "", L.Location("a buffer", -1)) ]
        @test evaluate("<!ENTITY % aé \"\">")   == [ InternalParameter("aé", "", L.Location("a buffer", -1)) ]
        @test evaluate("<!ENTITY % éb \"\">")   == [ InternalParameter("éb", "", L.Location("a buffer", -1)) ]
        @test evaluate("<!ENTITY % aéb \"\">")  == [ InternalParameter("aéb", "", L.Location("a buffer", -1)) ]
        @test evaluate("<!ENTITY\n%\na\n\"\">") == [ InternalParameter("a", "", L.Location("a buffer", -1)) ]
        @test evaluate("<!ENTITY\n%\ta\t\"\">") == [ InternalParameter("a", "", L.Location("a buffer", -1)) ]

        # Basic tests ... non-empty entity value.
        #
        @test evaluate("<!ENTITY % e \"a\">")       == [ InternalParameter("e", "a", L.Location("a buffer", -1)) ]
        @test evaluate("<!ENTITY % e \"abc\">")     == [ InternalParameter("e", "abc", L.Location("a buffer", -1)) ]
        @test evaluate("<!ENTITY % e \"é\">")       == [ InternalParameter("e", "é", L.Location("a buffer", -1)) ]
        @test evaluate("<!ENTITY % e \"aé\">")      == [ InternalParameter("e", "aé", L.Location("a buffer", -1)) ]
        @test evaluate("<!ENTITY % e \"éb\">")      == [ InternalParameter("e", "éb", L.Location("a buffer", -1)) ]
        @test evaluate("<!ENTITY % e \"aéb\">")     == [ InternalParameter("e", "aéb", L.Location("a buffer", -1)) ]
        @test evaluate("<!ENTITY % e \"\na\n\">")   == [ InternalParameter("e", "\na\n", L.Location("a buffer", -1)) ]
        @test evaluate("<!ENTITY % e \"\na\t\">")   == [ InternalParameter("e", "\na\t", L.Location("a buffer", -1)) ]
        @test evaluate("<!ENTITY % e \"&other;\">") == [ InternalParameter("e", "&other;", L.Location("a buffer", -1)) ]

        # This is from the XML specification, § 4.4.5 ...
        #
        @test evaluate("<!ENTITY % YN \'\"Yes\"\' >") == [ InternalParameter("YN", "\"Yes\"", L.Location("a buffer", -1)) ]
    end

    @testset "Events/Entity Declarations, Internal Parameter (Negative ... invalid or absent entity name.)" begin
        @test (evaluate("<!ENTITY %")
               == [ ME("ERROR: White space is required following '%'.", L.Location("a buffer", -1)),
                    ME("ERROR: Expecting an entity name.", L.Location("a buffer", -1)) ])

        events = evaluate("<!ENTITY %>")
        @test length(events) == 3
        @test (events == [ ME("ERROR: White space is required following '%'.", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting an entity name.", L.Location("a buffer", -1)),
                           DC(">", false, L.Location("a buffer", -1)) ])

        events = evaluate("<!ENTITY % >")
        @test length(events) == 2
        @test (events == [ ME("ERROR: Expecting an entity name.", L.Location("a buffer", -1)),
                           DC(">", false, L.Location("a buffer", -1)) ])

        # This one is a little weird: we're picking up "PUBLIC" as the entity name. I'll have to figure out later if
        # that's allowed or not, but there doesn't appear to be anything in the specification that forbids it.
        #
        events = evaluate("<!ENTITY % PUBLIC")
        @test length(events) == 4
        @test (events == [ ME("ERROR: White space is required between the entity name and the entity value.",
                              L.Location("a buffer", -1)),
                           ME("ERROR: Expecting a quoted string.", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting '>' to end an entity declaration.", L.Location("a buffer", -1)),
                           ExternalParameter("PUBLIC", L.Location("a buffer", -1)) ])

        events = evaluate("<!ENTITY % \"\"")
        @test length(events) == 3
        @test (events == [ ME("ERROR: Expecting an entity name.", L.Location("a buffer", -1)),
                           DC("\"", false, L.Location("a buffer", -1)),
                           DC("\"", false, L.Location("a buffer", -1)) ])

        events = evaluate("<!ENTITY % ''")
        @test length(events) == 3
        @test (events == [ ME("ERROR: Expecting an entity name.", L.Location("a buffer", -1)),
                           DC("'", false, L.Location("a buffer", -1)),
                           DC("'", false, L.Location("a buffer", -1)) ])

        events = evaluate("<!ENTITY % <")
        @test length(events) == 2
        @test (events == [ ME("ERROR: Expecting an entity name.", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting an element name.", L.Location("a buffer", -1)) ])
    end

    @testset "Events/Entity Declarations, Internal Parameter (Negative ... missing entity value.)" begin
        events = evaluate("<!ENTITY % e")
        @test length(events) == 4
        @test (events == [ ME("ERROR: White space is required between the entity name and the entity value.",
                              L.Location("a buffer", -1)),
                           ME("ERROR: Expecting a quoted string.", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting '>' to end an entity declaration.", L.Location("a buffer", -1)),
                           ExternalParameter("e", L.Location("a buffer", -1)) ])

        events = evaluate("<!ENTITY % e ")
        @test length(events) == 3
        @test (events == [ ME("ERROR: Expecting a quoted string.", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting '>' to end an entity declaration.", L.Location("a buffer", -1)),
                           ExternalParameter("e", L.Location("a buffer", -1)) ])

        events = evaluate("<!ENTITY % e <")
        @test length(events) == 4
        @test (events == [ ME("ERROR: Expecting a quoted string.", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting '>' to end an entity declaration.", L.Location("a buffer", -1)),
                           ExternalParameter("e", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting an element name.", L.Location("a buffer", -1)) ])
    end

    @testset "Events/Entity Declarations, Internal Parameter (Negative ... missing TAGC.)" begin
        events = evaluate("<!ENTITY % e \"value\"")
        @test length(events) == 2
        @test (events == [ ME("ERROR: Expecting '>' to end an entity declaration.", L.Location("a buffer", -1)),
                           InternalParameter("e", "value", L.Location("a buffer", -1)) ])

        events = evaluate("<!ENTITY % e \"value\" ")
        @test length(events) == 2
        @test (events == [ ME("ERROR: Expecting '>' to end an entity declaration.", L.Location("a buffer", -1)),
                           InternalParameter("e", "value", L.Location("a buffer", -1)) ])

        events = evaluate("<!ENTITY % e \"value\" <")
        @test length(events) == 3
        @test (events == [ ME("ERROR: Expecting '>' to end an entity declaration.", L.Location("a buffer", -1)),
                           InternalParameter("e", "value", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting an element name.", L.Location("a buffer", -1)) ])
    end

    @testset "Events/Entity Declarations, External General (Positive)" begin
        # Basic tests ... fixed public/system identifier.
        #
        @test (evaluate("<!ENTITY a PUBLIC \"hello.ent\" \"salut.ent\">")
               == [ ExternalGeneralText("a", "hello.ent", "salut.ent", L.Location("a buffer", -1)) ])

        @test (evaluate("<!ENTITY abc PUBLIC \"hello.ent\" \"salut.ent\">")
               == [ ExternalGeneralText("abc", "hello.ent", "salut.ent", L.Location("a buffer", -1)) ])

        @test (evaluate("<!ENTITY é PUBLIC \"hello.ent\" \"salut.ent\">")
               == [ ExternalGeneralText("é", "hello.ent", "salut.ent", L.Location("a buffer", -1)) ])

        @test (evaluate("<!ENTITY aé PUBLIC \"hello.ent\" \"salut.ent\">")
               == [ ExternalGeneralText("aé", "hello.ent", "salut.ent", L.Location("a buffer", -1)) ])

        @test (evaluate("<!ENTITY éb PUBLIC \"hello.ent\" \"salut.ent\">")
               == [ ExternalGeneralText("éb", "hello.ent", "salut.ent", L.Location("a buffer", -1)) ])

        @test (evaluate("<!ENTITY aéb PUBLIC \"hello.ent\" \"salut.ent\">")
               == [ ExternalGeneralText("aéb", "hello.ent", "salut.ent", L.Location("a buffer", -1)) ])

        @test (evaluate("<!ENTITY\na\nPUBLIC \"hello.ent\" \"salut.ent\">")
               == [ ExternalGeneralText("a", "hello.ent", "salut.ent", L.Location("a buffer", -1)) ])

        @test (evaluate("<!ENTITY\na\tPUBLIC \"hello.ent\" \"salut.ent\">")
               == [ ExternalGeneralText("a", "hello.ent", "salut.ent", L.Location("a buffer", -1)) ])

        # Basic tests ... fixed entity name ...
        #
        @test (evaluate("<!ENTITY a SYSTEM \"\">") == [ ExternalGeneralText("a", nothing, "", L.Location("a buffer", -1)) ])
        @test (evaluate("<!ENTITY a\nSYSTEM\t\"\"	>")
               == [ ExternalGeneralText("a", nothing, "", L.Location("a buffer", -1)) ])

        @test (evaluate("<!ENTITY a SYSTEM \"   \">") == [ ExternalGeneralText("a", nothing, "   ", L.Location("a buffer", -1)) ])

        @test (evaluate("<!ENTITY a PUBLIC \"\" \"\">") == [ ExternalGeneralText("a", "", "", L.Location("a buffer", -1)) ])
        @test (evaluate("<!ENTITY a\nPUBLIC\r\"\"\t\"\"	>") == [ ExternalGeneralText("a", "", "", L.Location("a buffer", -1)) ])
        @test (evaluate("<!ENTITY a PUBLIC \"   \" \"   \">")
               == [ ExternalGeneralText("a", "   ", "   ", L.Location("a buffer", -1)) ])

        @test (evaluate("<!ENTITY a SYSTEM \"hello.ent\">")
               == [ ExternalGeneralText("a", nothing, "hello.ent", L.Location("a buffer", -1)) ])

        # ... fixed entity name with system identifier ...
        #
        @test (evaluate("<!ENTITY a SYSTEM \"hello.ent\">")
               == [ ExternalGeneralText("a", nothing, "hello.ent", L.Location("a buffer", -1)) ])

        @test (evaluate("<!ENTITY a SYSTEM 'hello.ent'>")
               == [ ExternalGeneralText("a", nothing, "hello.ent", L.Location("a buffer", -1)) ])

        @test (evaluate("<!ENTITY a SYSTEM '\"hello.ent\"'>")
               == [ ExternalGeneralText("a", nothing, "\"hello.ent\"", L.Location("a buffer", -1)) ])

        @test (evaluate("<!ENTITY a SYSTEM \"'hello.ent'\">")
               == [ ExternalGeneralText("a", nothing, "'hello.ent'", L.Location("a buffer", -1)) ])

        # ... fixed entity name with public/system identifier ...
        #
        @test (evaluate("<!ENTITY a PUBLIC \"salut.ent\" \"hello.ent\">")
               == [ ExternalGeneralText("a", "salut.ent", "hello.ent", L.Location("a buffer", -1)) ])

        @test (evaluate("<!ENTITY a PUBLIC 'salut.ent' 'hello.ent'>")
               == [ ExternalGeneralText("a", "salut.ent", "hello.ent", L.Location("a buffer", -1)) ])

        @test (evaluate("<!ENTITY a PUBLIC '\"salut.ent\"' '\"hello.ent\"'>")
               == [ ExternalGeneralText("a", "\"salut.ent\"", "\"hello.ent\"", L.Location("a buffer", -1)) ])

        @test (evaluate("<!ENTITY a PUBLIC \"'salut.ent'\" \"'hello.ent'\">")
               == [ ExternalGeneralText("a", "'salut.ent'", "'hello.ent'", L.Location("a buffer", -1)) ])

        # This is from the XML specification, § 4.2.2 ...
        #
        @test (evaluate("<!ENTITY open-hatch"
                        * " PUBLIC \"-//Textuality//TEXT Standard open-hatch boilerplate//EN\""
                        * " \"http://www.textuality.com/boilerplate/OpenHatch.xml\">")
               == [ ExternalGeneralText("open-hatch", "-//Textuality//TEXT Standard open-hatch boilerplate//EN",
                                        "http://www.textuality.com/boilerplate/OpenHatch.xml", L.Location("a buffer", -1)) ])
    end

    @testset "Events/Entity Declarations, External General (Negative ... missing entity value.)" begin
        events = evaluate("<!ENTITY e PUBLIC")
        @test length(events) == 4
        @test (events == [ ME("ERROR: Expecting a quoted string.", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting a quoted string.", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting '>' to end an entity declaration.", L.Location("a buffer", -1)),
                           ExternalGeneralText("e", L.Location("a buffer", -1)) ])

        events = evaluate("<!ENTITY e PUBLIC ")
        @test length(events) == 4
        @test (events == [ ME("ERROR: Expecting a quoted string.", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting a quoted string.", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting '>' to end an entity declaration.", L.Location("a buffer", -1)),
                           ExternalGeneralText("e", L.Location("a buffer", -1)) ])

        events = evaluate("<!ENTITY e PUBLIC <")
        @test length(events) == 5
        @test (events == [ ME("ERROR: Expecting a quoted string.", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting a quoted string.", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting '>' to end an entity declaration.", L.Location("a buffer", -1)),
                           ExternalGeneralText("e", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting an element name.", L.Location("a buffer", -1)) ])

        events = evaluate("<!ENTITY e PUBLIC \"salut.ent\"")
        @test length(events) == 5
        @test (events == [ ME("ERROR: Expecting white space following a public identifier.", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting a quoted string.", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting a system identifier following a public identifier.",
                              L.Location("a buffer", -1)),
                           ME("ERROR: Expecting '>' to end an entity declaration.", L.Location("a buffer", -1)),
                           ExternalGeneralText("e", "salut.ent", nothing, L.Location("a buffer", -1)) ])

        events = evaluate("<!ENTITY e PUBLIC \"salut.ent\" ")
        @test length(events) == 4
        @test (events == [ ME("ERROR: Expecting a quoted string.", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting a system identifier following a public identifier.",
                              L.Location("a buffer", -1)),
                           ME("ERROR: Expecting '>' to end an entity declaration.", L.Location("a buffer", -1)),
                           ExternalGeneralText("e", "salut.ent", nothing, L.Location("a buffer", -1)) ])

        events = evaluate("<!ENTITY e PUBLIC \"salut.ent\" <")
        @test length(events) == 5
        @test (events == [ ME("ERROR: Expecting a quoted string.", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting a system identifier following a public identifier.",
                              L.Location("a buffer", -1)),
                           ME("ERROR: Expecting '>' to end an entity declaration.", L.Location("a buffer", -1)),
                           ExternalGeneralText("e", "salut.ent", nothing, L.Location("a buffer", -1)),
                           ME("ERROR: Expecting an element name.", L.Location("a buffer", -1)) ])

        events = evaluate("<!ENTITY e SYSTEM")
        @test length(events) == 4
        @test (events == [ ME("ERROR: Expecting a quoted string.", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting a quoted string.", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting '>' to end an entity declaration.", L.Location("a buffer", -1)),
                           ExternalGeneralText("e", L.Location("a buffer", -1)) ])

        events = evaluate("<!ENTITY e SYSTEM ")
        @test length(events) == 4
        @test (events == [ ME("ERROR: Expecting a quoted string.", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting a quoted string.", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting '>' to end an entity declaration.", L.Location("a buffer", -1)),
                           ExternalGeneralText("e", L.Location("a buffer", -1)) ])

        events = evaluate("<!ENTITY e SYSTEM <")
        @test length(events) == 5
        @test (events == [ ME("ERROR: Expecting a quoted string.", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting a quoted string.", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting '>' to end an entity declaration.", L.Location("a buffer", -1)),
                           ExternalGeneralText("e", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting an element name.", L.Location("a buffer", -1)) ])
    end

    @testset "Events/Entity Declarations, External General (Negative ... missing TAGC.)" begin
        events = evaluate("<!ENTITY e PUBLIC \"salut.ent\" \"hello.ent\"")
        @test length(events) == 2
        @test (events == [ ME("ERROR: Expecting '>' to end an entity declaration.", L.Location("a buffer", -1)),
                           ExternalGeneralText("e", "salut.ent", "hello.ent", L.Location("a buffer", -1)) ])

        events = evaluate("<!ENTITY e PUBLIC \"salut.ent\" \"hello.ent\" ")
        @test length(events) == 2
        @test (events == [ ME("ERROR: Expecting '>' to end an entity declaration.", L.Location("a buffer", -1)),
                           ExternalGeneralText("e", "salut.ent", "hello.ent", L.Location("a buffer", -1)) ])

        events = evaluate("<!ENTITY e PUBLIC \"salut.ent\" \"hello.ent\" <")
        @test length(events) == 3
        @test (events == [ ME("ERROR: Expecting '>' to end an entity declaration.", L.Location("a buffer", -1)),
                           ExternalGeneralText("e", "salut.ent", "hello.ent", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting an element name.", L.Location("a buffer", -1)) ])
    end

    @testset "Events/Entity Declarations, External General (Negative ... lowercased keywords.)" begin
        events = evaluate("<!entity e public \"salut.ent\" \"hello.ent\">")
        @test length(events) == 3
        @test (events == [ ME("ERROR: The keyword 'entity' must be uppercased.", L.Location("a buffer", -1)),
                           ME("ERROR: The keyword 'public' must be uppercased.", L.Location("a buffer", -1)),
                           ExternalGeneralText("e", "salut.ent", "hello.ent", L.Location("a buffer", -1)) ])

        events = evaluate("<!entity e system \"hello.ent\">")
        @test length(events) == 3
        @test (events == [ ME("ERROR: The keyword 'entity' must be uppercased.", L.Location("a buffer", -1)),
                           ME("ERROR: The keyword 'system' must be uppercased.", L.Location("a buffer", -1)),
                           ExternalGeneralText("e", nothing, "hello.ent", L.Location("a buffer", -1)) ])
    end

    @testset ("""Events/Entity Declarations, External General
                  (Negative ... missing white space between public and system identifier.)""") begin
        events = evaluate("<!ENTITY e PUBLIC \"salut.ent\"\"hello.ent\"")
        @test length(events) == 3
        @test (events == [ ME("ERROR: Expecting white space following a public identifier.", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting '>' to end an entity declaration.", L.Location("a buffer", -1)),
                           ExternalGeneralText("e", "salut.ent", "hello.ent", L.Location("a buffer", -1)) ] )
    end

    @testset "Events/Entity Declarations, External Parameter (Positive)" begin
        # Basic tests ... fixed public/system identifier.
        #
        @test (evaluate("<!ENTITY % a PUBLIC \"hello.ent\" \"salut.ent\">")
               == [ ExternalParameter("a", "hello.ent", "salut.ent", L.Location("a buffer", -1)) ])

        @test (evaluate("<!ENTITY % abc PUBLIC \"hello.ent\" \"salut.ent\">")
               == [ ExternalParameter("abc", "hello.ent", "salut.ent", L.Location("a buffer", -1)) ])

        @test (evaluate("<!ENTITY % é PUBLIC \"hello.ent\" \"salut.ent\">")
               == [ ExternalParameter("é", "hello.ent", "salut.ent", L.Location("a buffer", -1)) ])

        @test (evaluate("<!ENTITY % aé PUBLIC \"hello.ent\" \"salut.ent\">")
               == [ ExternalParameter("aé", "hello.ent", "salut.ent", L.Location("a buffer", -1)) ])

        @test (evaluate("<!ENTITY % éb PUBLIC \"hello.ent\" \"salut.ent\">")
               == [ ExternalParameter("éb", "hello.ent", "salut.ent", L.Location("a buffer", -1)) ])

        @test (evaluate("<!ENTITY % aéb PUBLIC \"hello.ent\" \"salut.ent\">")
               == [ ExternalParameter("aéb", "hello.ent", "salut.ent", L.Location("a buffer", -1)) ])

        @test (evaluate("<!ENTITY %\na\nPUBLIC \"hello.ent\" \"salut.ent\">")
               == [ ExternalParameter("a", "hello.ent", "salut.ent", L.Location("a buffer", -1)) ])

        @test (evaluate("<!ENTITY %\na\tPUBLIC \"hello.ent\" \"salut.ent\">")
               == [ ExternalParameter("a", "hello.ent", "salut.ent", L.Location("a buffer", -1)) ])

        # Basic tests ... fixed entity name ...
        #
        @test (evaluate("<!ENTITY % a SYSTEM \"\">") == [ ExternalParameter("a", nothing, "", L.Location("a buffer", -1)) ])
        @test (evaluate("<!ENTITY % a\nSYSTEM\t\"\"	>") == [ ExternalParameter("a", nothing, "", L.Location("a buffer", -1)) ])
        @test (evaluate("<!ENTITY % a SYSTEM \"   \">") == [ ExternalParameter("a", nothing, "   ", L.Location("a buffer", -1)) ])
        @test (evaluate("<!ENTITY % a PUBLIC \"\" \"\">") == [ ExternalParameter("a", "", "", L.Location("a buffer", -1)) ])
        @test (evaluate("<!ENTITY % a\nPUBLIC\r\"\"\t\"\"	>")
               == [ ExternalParameter("a", "", "", L.Location("a buffer", -1)) ])

        @test (evaluate("<!ENTITY % a PUBLIC \"   \" \"   \">")
               == [ ExternalParameter("a", "   ", "   ", L.Location("a buffer", -1)) ])

        @test (evaluate("<!ENTITY % a SYSTEM \"hello.ent\">")
               == [ ExternalParameter("a", nothing, "hello.ent", L.Location("a buffer", -1)) ])

        # ... fixed entity name with system identifier ...
        #
        @test (evaluate("<!ENTITY % a SYSTEM \"hello.ent\">")
               == [ ExternalParameter("a", nothing, "hello.ent", L.Location("a buffer", -1)) ])

        @test (evaluate("<!ENTITY % a SYSTEM 'hello.ent'>")
               == [ ExternalParameter("a", nothing, "hello.ent", L.Location("a buffer", -1)) ])

        @test (evaluate("<!ENTITY % a SYSTEM '\"hello.ent\"'>")
               == [ ExternalParameter("a", nothing, "\"hello.ent\"", L.Location("a buffer", -1)) ])

        @test (evaluate("<!ENTITY % a SYSTEM \"'hello.ent'\">")
               == [ ExternalParameter("a", nothing, "'hello.ent'", L.Location("a buffer", -1)) ])

        # ... fixed entity name with public/system identifier ...
        #
        @test (evaluate("<!ENTITY % a PUBLIC \"salut.ent\" \"hello.ent\">")
               == [ ExternalParameter("a", "salut.ent", "hello.ent", L.Location("a buffer", -1)) ])

        @test (evaluate("<!ENTITY % a PUBLIC 'salut.ent' 'hello.ent'>")
               == [ ExternalParameter("a", "salut.ent", "hello.ent", L.Location("a buffer", -1)) ])

        @test (evaluate("<!ENTITY % a PUBLIC '\"salut.ent\"' '\"hello.ent\"'>")
               == [ ExternalParameter("a", "\"salut.ent\"", "\"hello.ent\"", L.Location("a buffer", -1)) ])

        @test (evaluate("<!ENTITY % a PUBLIC \"'salut.ent'\" \"'hello.ent'\">")
               == [ ExternalParameter("a", "'salut.ent'", "'hello.ent'", L.Location("a buffer", -1)) ])

        # This is from the XML specification, § 4.1 ...
        #
        @test (evaluate("<!ENTITY % ISOLat2 SYSTEM \"http://www.xml.com/iso/isolat2-xml.entities\" >")
               == [ ExternalParameter("ISOLat2", nothing, "http://www.xml.com/iso/isolat2-xml.entities",
                                      L.Location("a buffer", -1)) ])
    end

    @testset "Events/Entity Declarations, External Parameter (Negative ... missing entity value.)" begin
        events = evaluate("<!ENTITY % e PUBLIC")
        @test length(events) == 4
        @test (events == [ ME("ERROR: Expecting a quoted string.", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting a quoted string.", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting '>' to end an entity declaration.", L.Location("a buffer", -1)),
                           ExternalParameter("e", L.Location("a buffer", -1)) ])

        events = evaluate("<!ENTITY % e PUBLIC ")
        @test length(events) == 4
        @test (events == [ ME("ERROR: Expecting a quoted string.", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting a quoted string.", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting '>' to end an entity declaration.", L.Location("a buffer", -1)),
                           ExternalParameter("e", L.Location("a buffer", -1)) ])

        events = evaluate("<!ENTITY % e PUBLIC <")
        @test length(events) == 5
        @test (events == [ ME("ERROR: Expecting a quoted string.", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting a quoted string.", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting '>' to end an entity declaration.", L.Location("a buffer", -1)),
                           ExternalParameter("e", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting an element name.", L.Location("a buffer", -1)) ])

        events = evaluate("<!ENTITY % e PUBLIC \"salut.ent\"")
        @test length(events) == 5
        @test (events == [ ME("ERROR: Expecting white space following a public identifier.", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting a quoted string.", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting a system identifier following a public identifier.",
                              L.Location("a buffer", -1)),
                           ME("ERROR: Expecting '>' to end an entity declaration.", L.Location("a buffer", -1)),
                           ExternalParameter("e", "salut.ent", nothing, L.Location("a buffer", -1)) ])

        events = evaluate("<!ENTITY % e PUBLIC \"salut.ent\" ")
        @test length(events) == 4
        @test (events == [ ME("ERROR: Expecting a quoted string.", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting a system identifier following a public identifier.",
                              L.Location("a buffer", -1)),
                           ME("ERROR: Expecting '>' to end an entity declaration.", L.Location("a buffer", -1)),
                           ExternalParameter("e", "salut.ent", nothing, L.Location("a buffer", -1)) ])

        events = evaluate("<!ENTITY % e PUBLIC \"salut.ent\" <")
        @test length(events) == 5
        @test (events == [ ME("ERROR: Expecting a quoted string.", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting a system identifier following a public identifier.",
                              L.Location("a buffer", -1)),
                           ME("ERROR: Expecting '>' to end an entity declaration.", L.Location("a buffer", -1)),
                           ExternalParameter("e", "salut.ent", nothing, L.Location("a buffer", -1)),
                           ME("ERROR: Expecting an element name.", L.Location("a buffer", -1)) ])

        events = evaluate("<!ENTITY % e SYSTEM")
        @test length(events) == 4
        @test (events == [ ME("ERROR: Expecting a quoted string.", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting a quoted string.", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting '>' to end an entity declaration.", L.Location("a buffer", -1)),
                           ExternalParameter("e", L.Location("a buffer", -1)) ])

        events = evaluate("<!ENTITY % e SYSTEM ")
        @test length(events) == 4
        @test (events == [ ME("ERROR: Expecting a quoted string.", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting a quoted string.", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting '>' to end an entity declaration.", L.Location("a buffer", -1)),
                           ExternalParameter("e", L.Location("a buffer", -1)) ])

        events = evaluate("<!ENTITY % e SYSTEM <")
        @test length(events) == 5
        @test (events == [ ME("ERROR: Expecting a quoted string.", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting a quoted string.", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting '>' to end an entity declaration.", L.Location("a buffer", -1)),
                           ExternalParameter("e", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting an element name.", L.Location("a buffer", -1)) ])
    end

    @testset ("""Events/Entity Declarations, External Parameter
                  (Negative ... missing white space between public and system identifier.)""") begin
        events = evaluate("<!ENTITY % e PUBLIC \"salut.ent\"\"hello.ent\"")
        @test length(events) == 3
        @test (events == [ ME("ERROR: Expecting white space following a public identifier.", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting '>' to end an entity declaration.", L.Location("a buffer", -1)),
                           ExternalParameter("e", "salut.ent", "hello.ent", L.Location("a buffer", -1)) ] )
    end

    @testset "Events/Entity Declarations, External Parameter (Negative ... missing TAGC.)" begin
        events = evaluate("<!ENTITY % e PUBLIC \"salut.ent\" \"hello.ent\"")
        @test length(events) == 2
        @test (events == [ ME("ERROR: Expecting '>' to end an entity declaration.", L.Location("a buffer", -1)),
                           ExternalParameter("e", "salut.ent", "hello.ent", L.Location("a buffer", -1)) ])

        events = evaluate("<!ENTITY % e PUBLIC \"salut.ent\" \"hello.ent\" ")
        @test length(events) == 2
        @test (events == [ ME("ERROR: Expecting '>' to end an entity declaration.", L.Location("a buffer", -1)),
                           ExternalParameter("e", "salut.ent", "hello.ent", L.Location("a buffer", -1)) ])

        events = evaluate("<!ENTITY % e PUBLIC \"salut.ent\" \"hello.ent\" <")
        @test length(events) == 3
        @test (events == [ ME("ERROR: Expecting '>' to end an entity declaration.", L.Location("a buffer", -1)),
                           ExternalParameter("e", "salut.ent", "hello.ent", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting an element name.", L.Location("a buffer", -1)) ])
    end

    @testset "Events/Entity Declarations, External General (Negative ... lowercased keywords.)" begin
        events = evaluate("<!entity % e public \"salut.ent\" \"hello.ent\">")
        @test length(events) == 3
        @test (events == [ ME("ERROR: The keyword 'entity' must be uppercased.", L.Location("a buffer", -1)),
                           ME("ERROR: The keyword 'public' must be uppercased.", L.Location("a buffer", -1)),
                           ExternalParameter("e", "salut.ent", "hello.ent", L.Location("a buffer", -1)) ])

        events = evaluate("<!entity % e system \"hello.ent\">")
        @test length(events) == 3
        @test (events == [ ME("ERROR: The keyword 'entity' must be uppercased.", L.Location("a buffer", -1)),
                           ME("ERROR: The keyword 'system' must be uppercased.", L.Location("a buffer", -1)),
                           ExternalParameter("e", nothing, "hello.ent", L.Location("a buffer", -1)) ])
    end

    @testset "Events/Entity Declarations, External Parameter (Negative ... attempted notation specification)" begin
        events = evaluate("<!ENTITY % a PUBLIC \"hello.ent\" \"salut.ent\" NDATA notation>")
        @test length(events) == 2
        @test (events == [ ME("ERROR: A parameter entity cannot have a notation.", L.Location("a buffer", -1)),
                           ExternalParameter("a", "hello.ent", "salut.ent", L.Location("a buffer", -1)) ])
    end

    @testset ("""Events/Entity Declarations, External Parameter
                  (Negative ... missing white space between public and system identifier.)""") begin
        events = evaluate("<!ENTITY % e PUBLIC \"salut.ent\"\"hello.ent\"")
        @test length(events) == 3
        @test (events == [ ME("ERROR: Expecting white space following a public identifier.", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting '>' to end an entity declaration.", L.Location("a buffer", -1)),
                           ExternalParameter("e", "salut.ent", "hello.ent", L.Location("a buffer", -1)) ] )
    end

    @testset "Events/Entity Declarations, External Data (Positive)" begin
        # Basic tests ... fixed entity name, fixed public/system identifier, with notation specification.
        #
        @test (evaluate("<!ENTITY e PUBLIC \"hello.ent\" \"salut.ent\" NDATA a>")
               == [ ExternalGeneralData("e", "hello.ent", "salut.ent", "a", L.Location("a buffer", -1)) ])

        @test (evaluate("<!ENTITY e PUBLIC \"hello.ent\" \"salut.ent\" NDATA abc>")
               == [ ExternalGeneralData("e", "hello.ent", "salut.ent", "abc", L.Location("a buffer", -1)) ])

        @test (evaluate("<!ENTITY e PUBLIC \"hello.ent\" \"salut.ent\" NDATA é>")
               == [ ExternalGeneralData("e", "hello.ent", "salut.ent", "é", L.Location("a buffer", -1)) ])

        @test (evaluate("<!ENTITY e PUBLIC \"hello.ent\" \"salut.ent\" NDATA aé>")
               == [ ExternalGeneralData("e", "hello.ent", "salut.ent", "aé", L.Location("a buffer", -1)) ])

        @test (evaluate("<!ENTITY e PUBLIC \"hello.ent\" \"salut.ent\" NDATA éb>")
               == [ ExternalGeneralData("e", "hello.ent", "salut.ent", "éb", L.Location("a buffer", -1)) ])

        @test (evaluate("<!ENTITY e PUBLIC \"hello.ent\" \"salut.ent\" NDATA aéb>")
               == [ ExternalGeneralData("e", "hello.ent", "salut.ent", "aéb", L.Location("a buffer", -1)) ])

        @test (evaluate("<!ENTITY e PUBLIC \"hello.ent\" \"salut.ent\" NDATA\na\n>")
               == [ ExternalGeneralData("e", "hello.ent", "salut.ent", "a", L.Location("a buffer", -1)) ])

        @test (evaluate("<!ENTITY e PUBLIC \"hello.ent\" \"salut.ent\" NDATA\na\t>")
               == [ ExternalGeneralData("e", "hello.ent", "salut.ent", "a", L.Location("a buffer", -1)) ])
    end

    @testset "Events/Entity Declarations, External Data (Negative ... missing TAGC.)" begin
        events = evaluate("<!ENTITY e PUBLIC \"salut.ent\" \"hello.ent\" NDATA notation")
        @test length(events) == 2
        @test (events == [ ME("ERROR: Expecting '>' to end an entity declaration.", L.Location("a buffer", -1)),
                           ExternalGeneralData("e", "salut.ent", "hello.ent", "notation", L.Location("a buffer", -1)) ])

        events = evaluate("<!ENTITY e PUBLIC \"salut.ent\" \"hello.ent\" NDATA notation ")
        @test length(events) == 2
        @test (events == [ ME("ERROR: Expecting '>' to end an entity declaration.", L.Location("a buffer", -1)),
                           ExternalGeneralData("e", "salut.ent", "hello.ent", "notation", L.Location("a buffer", -1)) ])

        events = evaluate("<!ENTITY e PUBLIC \"salut.ent\" \"hello.ent\" NDATA notation <")
        @test length(events) == 3
        @test (events == [ ME("ERROR: Expecting '>' to end an entity declaration.", L.Location("a buffer", -1)),
                           ExternalGeneralData("e", "salut.ent", "hello.ent", "notation", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting an element name.", L.Location("a buffer", -1)) ])
    end

    @testset "Events/Entity Declarations, External Data (Negative ... lowercased keywords)" begin
        events = evaluate("<!entity e public \"salut.ent\" \"hello.ent\" ndata notation>")
        @test length(events) == 4
        @test (events == [ ME("ERROR: The keyword 'entity' must be uppercased.", L.Location("a buffer", -1)),
                           ME("ERROR: The keyword 'public' must be uppercased.", L.Location("a buffer", -1)),
                           ME("ERROR: The keyword 'ndata' must be uppercased.", L.Location("a buffer", -1)),
                           ExternalGeneralData("e", "salut.ent", "hello.ent", "notation", L.Location("a buffer", -1)) ])

        events = evaluate("<!entity e system \"hello.ent\" ndata notation>")
        @test length(events) == 4
        @test (events == [ ME("ERROR: The keyword 'entity' must be uppercased.", L.Location("a buffer", -1)),
                           ME("ERROR: The keyword 'system' must be uppercased.", L.Location("a buffer", -1)),
                           ME("ERROR: The keyword 'ndata' must be uppercased.", L.Location("a buffer", -1)),
                           ExternalGeneralData("e", nothing, "hello.ent", "notation", L.Location("a buffer", -1)) ])
    end

    @testset "Events/Entity Declarations, External Data (Negative ... invalid or absent notation name)" begin
        events = evaluate("<!ENTITY e PUBLIC \"hello.ent\" \"salut.ent\" NDATA")
        @test length(events) == 3
        @test (events == [ ME("ERROR: Expecting a notation name.", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting '>' to end an entity declaration.", L.Location("a buffer", -1)),
                           ExternalGeneralText("e", "hello.ent", "salut.ent", L.Location("a buffer", -1)) ])

        events = evaluate("<!ENTITY e PUBLIC \"hello.ent\" \"salut.ent\" NDATA>")
        @test length(events) == 2
        @test (events == [ ME("ERROR: Expecting a notation name.", L.Location("a buffer", -1)),
                           ExternalGeneralText("e", "hello.ent", "salut.ent", L.Location("a buffer", -1)) ])

        events = evaluate("<!ENTITY e PUBLIC \"hello.ent\" \"salut.ent\" NDATA >")
        @test length(events) == 2
        @test (events == [ ME("ERROR: Expecting a notation name.", L.Location("a buffer", -1)),
                           ExternalGeneralText("e", "hello.ent", "salut.ent", L.Location("a buffer", -1)) ])

        events = evaluate("<!ENTITY e PUBLIC \"hello.ent\" \"salut.ent\" NDATA \"notation\">")
        @test length(events) == 7
        @test (events == [ ME("ERROR: Expecting a notation name.", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting '>' to end an entity declaration.", L.Location("a buffer", -1)),
                           ExternalGeneralText("e", "hello.ent", "salut.ent", L.Location("a buffer", -1)),
                           DC("\"", false, L.Location("a buffer", -1)),
                           DC("notation", false, L.Location("a buffer", -1)),
                           DC("\"", false, L.Location("a buffer", -1)),
                           DC(">", false, L.Location("a buffer", -1)) ])

        events = evaluate("<!ENTITY e PUBLIC \"hello.ent\" \"salut.ent\" NDATA \'notation\'>")
        @test length(events) == 7
        @test (events == [ ME("ERROR: Expecting a notation name.", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting '>' to end an entity declaration.", L.Location("a buffer", -1)),
                           ExternalGeneralText("e", "hello.ent", "salut.ent", L.Location("a buffer", -1)),
                           DC("'", false, L.Location("a buffer", -1)),
                           DC("notation", false, L.Location("a buffer", -1)),
                           DC("'", false, L.Location("a buffer", -1)),
                           DC(">", false, L.Location("a buffer", -1)) ])

        events = evaluate("<!ENTITY e PUBLIC \"hello.ent\" \"salut.ent\" NDATA <")
        @test length(events) == 4
        @test (events == [ ME("ERROR: Expecting a notation name.", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting '>' to end an entity declaration.", L.Location("a buffer", -1)),
                           ExternalGeneralText("e", "hello.ent", "salut.ent", L.Location("a buffer", -1)),
                           ME("ERROR: Expecting an element name.", L.Location("a buffer", -1)) ])
    end
end

max_stars_repo_name: jlegare/NativeXML

max_issues_repo_name: jlegare/NativeXML

max_forks_repo_name: jlegare/NativeXML


text:
function set_property(jprops::JProperties, key::AbstractString, value::AbstractString)
    jcall(jprops, "setProperty", JObject, (JString, JString), key, value)
end

function get_property(jprops::JProperties, key::AbstractString)
    return jcall(jprops, "getProperty", JString, (JString,), key)
end

function to_jprops(d::Dict{String, String})
    jprops = JProperties(())
    for (k, v) in d
        set_property(jprops, k, v)
    end
    return jprops
end

ext: jl


text:
# constructing:
start_time = time()
# long computation
time_elapsed = time() - start_time
println("Time elapsed: $time_elapsed") #>  0.0009999275207519531

using Dates
d = Date(2018,9,1) #> 2018-09-01
dt = DateTime(2018,9,1,12,30,59,1) #> 2018-09-01T12:30:59.001
# accessors:
year(d)
month(d)
week(d)
day(d)
# functions:
isleapyear(d)
dayofyear(d)
monthname(d)
daysinmonth(d)


text:
"""
    WienerKernel(; i::Int=0)
    WienerKernel{i}()

The `i`-times integrated Wiener process kernel function.

# Definition

For inputs ``x, x' \\in \\mathbb{R}^d``, the ``i``-times integrated Wiener process kernel
with ``i \\in \\{-1, 0, 1, 2, 3\\}`` is defined[^SDH] as
```math
k_i(x, x') = \\begin{cases}
    \\delta(x, x') & \\text{if } i=-1,\\\\
    \\min\\big(\\|x\\|_2, \\|x'\\|_2\\big) & \\text{if } i=0,\\\\
    a_{i1}^{-1} \\min\\big(\\|x\\|_2, \\|x'\\|_2\\big)^{2i + 1}
    + a_{i2}^{-1} \\|x - x'\\|_2 r_i\\big(\\|x\\|_2, \\|x'\\|_2\\big) \\min\\big(\\|x\\|_2, \\|x'\\|_2\\big)^{i + 1}
    & \\text{otherwise},
\\end{cases}
```
where the coefficients ``a`` are given by
```math
a = \\begin{bmatrix}
3 & 2 \\\\
20 & 12 \\\\
252 & 720
\\end{bmatrix}
```
and the functions ``r_i`` are defined as
```math
\\begin{aligned}
r_1(t, t') &= 1,\\\\
r_2(t, t') &= t + t' - \\frac{\\min(t, t')}{2},\\\\
r_3(t, t') &= 5 \\max(t, t')^2 + 2 tt' + 3 \\min(t, t')^2.
\\end{aligned}
```

The [`WhiteKernel`](@ref) is recovered for ``i = -1``.

[^SDH]: Schober, Duvenaud & Hennig (2014). Probabilistic ODE Solvers with Runge-Kutta Means.
"""
struct WienerKernel{I} <: Kernel
    function WienerKernel{I}() where {I}
        @check_args(WienerKernel, I, I ∈ (-1, 0, 1, 2, 3), "I ∈ {-1, 0, 1, 2, 3}")
        if I == -1
            return WhiteKernel()
        end
        return new{I}()
    end
end

function WienerKernel(; i::Integer=0)
    return WienerKernel{i}()
end

function (::WienerKernel{0})(x, y)
    X = sqrt(sum(abs2, x))
    Y = sqrt(sum(abs2, y))
    return min(X, Y)
end

function (::WienerKernel{1})(x, y)
    X = sqrt(sum(abs2, x))
    Y = sqrt(sum(abs2, y))
    minXY = min(X, Y)
    return 1 / 3 * minXY^3 + 1 / 2 * minXY^2 * euclidean(x, y)
end

function (::WienerKernel{2})(x, y)
    X = sqrt(sum(abs2, x))
    Y = sqrt(sum(abs2, y))
    minXY = min(X, Y)
    return 1 / 20 * minXY^5 + 1 / 12 * minXY^3 * euclidean(x, y) * (X + Y - 1 / 2 * minXY)
end

function (::WienerKernel{3})(x, y)
    X = sqrt(sum(abs2, x))
    Y = sqrt(sum(abs2, y))
    minXY = min(X, Y)
    return 1 / 252 * minXY^7 +
           1 / 720 * minXY^4 * euclidean(x, y) * (5 * max(X, Y)^2 + 2 * X * Y + 3 * minXY^2)
end

function Base.show(io::IO, ::WienerKernel{I}) where {I}
    return print(io, I, "-times integrated Wiener kernel")
end


text:
using Juliaset

Juliaset.serve_local()


text:
#############################################################################
#Copyright (c) 2016 Charles Le Losq
#
#The MIT License (MIT)
#
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the #Software without restriction, including without limitation the rights to use, copy, #modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, #and to permit persons to whom the Software is furnished to do so, subject to the #following conditions:
#
#The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
#
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, #INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# This files contains functions to perform calculations of numeric integrale values on real dataset x y.
#
#
#############################################################################

"""
The 'trapz' function allows to perform numerical integration of a signal.
	integrale (float64) = trapz(x:vector of float 64, y:vector of float 64)
"""
function trapz{Tx<:Number, Ty<:Number}(x::Vector{Tx}, y::Vector{Ty})
    # Trapezoidal integration rule
    local n = length(x)
    if (length(y) != n)
        error("Vectors 'x', 'y' must be of same length")
    end
    r = zero(zero(Tx) + zero(Ty))
    if n == 1; return r; end
    for i in 2:n
        r += (x[i] - x[i-1]) * (y[i] + y[i-1])
    end
    trapz_int = r/2
    return trapz_int
end

#function trapz(y, x)
#    r = 0
#    for i = 1:(length(x)-1)
#        r += 0.5 .* (x[i+1]-x[i]).*(y[i+1] + y[i])
#    end
#    return r
#end


text:
### Initial conditions

# body mass ratios
allZ = [1, 10, 100]

# fertilisation gradient
allI_K = [1:1:20;]

# temperature gradient
allT = [0:1:40;] .+ 273.15

# time span for each simulation
tspan = (0.0, 315360000000.0)

# define extinction threshold
threshold = 10e-12

#### data frame to store outputs
df = DataFrame(
    Z = [],
    I_K = [],
    T = [],
    K = [],
    r = [],
    x_2 = [],
    y_21 = [],
    B0_21 = [],
    maxB1 = [],
    maxB2 = [],
    maxB3 = [],
    minB1 = [],
    minB2 = [],
    minB3 = [],
    sp_survival = []
)

### Simulation loops
for Z in allZ
    for I_K in allI_K
        for T in allT

            # generate parameters specific to this combo of Z, I_K and T
            p = ModelParameters(param, T, I_K, Z)

            # Calculate starting biomasses
            u0 = [p.K[1]/2, p.K[1]/2, p.K[1]/2]

            # Define the problem
            prob = ODEProblem(BEFW, u0, tspan, p)

            # Solve the problem
            sol = solve(prob, maxiters = 1e7)

            ###### Output metrics ######
            # turn vector output of biomasses into matrix
            matrix_u = hcat(sol.u...)' # 56×3 adjoint(::Matrix{Float64}) with eltype Float64:

            ### Biomass extremes
            maxB1 = maximum(matrix_u[2 * Int64(round(length(matrix_u[:,1])/3)):Int64(length(matrix_u[:,1])),1])
            maxB2 = maximum(matrix_u[2 * Int64(round(length(matrix_u[:,1])/3)):Int64(length(matrix_u[:,1])),2])
            maxB3 = maximum(matrix_u[2 * Int64(round(length(matrix_u[:,1])/3)):Int64(length(matrix_u[:,1])),3])

            minB1 = minimum(matrix_u[2 * Int64(round(length(matrix_u[:,1])/3)):Int64(length(matrix_u[:,1])),1])
            minB2 = minimum(matrix_u[2 * Int64(round(length(matrix_u[:,1])/3)):Int64(length(matrix_u[:,1])),2])
            minB3 = minimum(matrix_u[2 * Int64(round(length(matrix_u[:,1])/3)):Int64(length(matrix_u[:,1])),3])

            ### Number of surviving species
            # final biomasses
            final_u = matrix_u[length(sol.t),:]

            # count number of species remaining that are above extinction threshold
            survived = count(final_u .> threshold)

            ### Push outputs to DataFrame
            push!(df, [
                Z,
                I_K,
                T,
                p.K[1],
                p.r[1],
                p.x[2],
                p.y[2,1],
                p.B0[2,1],
                maxB1,
                maxB2,
                maxB3,
                minB1,
                minB2,
                minB3,
                survived
            ])

            # print some stuff - see how the simulation is progressing
            println(("Z = $Z", "I_K = $I_K", "T = $T"))

        end
    end
end

# u0
# typeof(u0)

# p

# plot(sol)
# sol.t

# sol.u #56-element Vector{Vector{Float64}}
# matrix_u = hcat(sol.u) # convert to 56×1 Matrix{Vector{Float64}}
# matrix_u = hcat(sol.u...)' # 56×3 adjoint(::Matrix{Float64}) with eltype Float64:

# final_u = matrix_u[length(sol.t),:]

# count(final_u .> threshold)

10000*365*24*60*60

# ### finding biomass extremes
# maximum(matrix_u[:,2])

maximum(eachrow(df))

Z = 1
T = 279.15
I_K = 9
# generate parameters specific to this combo of Z, I_K and T
p = ModelParameters(param, T, I_K, Z)

# Calculate starting biomasses
u0 = [p.K[1]/2, p.K[1]/2, p.K[1]/2]

# time span for each simulation
tspan = (0.0, 315360000000.0)

# Define the problem
prob = ODEProblem(BEFW, u0, tspan, p)

# Solve the problem
sol = solve(prob)

plot(sol)

max_stars_repo_name: hanamayall/simple-feeding-DR

max_issues_repo_name: hanamayall/simple-feeding-DR

max_forks_repo_name: hanamayall/simple-feeding-DR


text:
const PyRange_Type__ref = Ref(PyNULL)
PyRange_Type() = begin
    ptr = PyRange_Type__ref[]
    isnull(ptr) || return ptr
    bs = PyEval_GetBuiltins()
    ptr = PyMapping_GetItemString(bs, "range")
    PyRange_Type__ref[] = ptr
end

PyRange_From(a::Integer) = begin
    t = PyRange_Type()
    isnull(t) && return t
    PyObject_CallNice(t, a)
end

PyRange_From(a::Integer, b::Integer) = begin
    t = PyRange_Type()
    isnull(t) && return t
    PyObject_CallNice(t, a, b)
end

PyRange_From(a::Integer, b::Integer, c::Integer) = begin
    t = PyRange_Type()
    isnull(t) && return t
    PyObject_CallNice(t, a, b, c)
end

PyRange_From(r::AbstractRange{<:Integer}) =
    PyRange_From(first(r), last(r) + sign(step(r)), step(r))

steptype(::Type{<:(StepRange{A,B} where {A})}) where {B} = B
steptype(::Type{<:StepRange}) = Any

PyRange_TryConvertRule_steprange(o, ::Type{S}) where {S<:StepRange} = begin
    A = _typeintersect(Integer, eltype(S))
    B = _typeintersect(Integer, steptype(S))
    # get start
    ao = PyObject_GetAttrString(o, "start")
    isnull(ao) && return -1
    r = PyObject_TryConvert(ao, A)
    Py_DecRef(ao)
    r == 1 || return r
    a = takeresult(A)
    # get step
    bo = PyObject_GetAttrString(o, "step")
    isnull(bo) && return -1
    r = PyObject_TryConvert(bo, B)
    Py_DecRef(bo)
    r == 1 || return r
    b = takeresult(B)
    # get stop
    co = PyObject_GetAttrString(o, "stop")
    isnull(co) && return -1
    r = PyObject_TryConvert(co, A)
    Py_DecRef(co)
    r == 1 || return r
    c = takeresult(A)
    # success
    a′, c′ = promote(a, c - oftype(c, sign(b)))
    putresult(tryconvert(S, StepRange(a′, b, c′)))
end

PyRange_TryConvertRule_unitrange(o, ::Type{S}) where {S<:UnitRange} = begin
    A = _typeintersect(Integer, eltype(S))
    # get step
    bo = PyObject_GetAttrString(o, "step")
    isnull(bo) && return -1
    r = PyObject_TryConvert(bo, Int)
    Py_DecRef(bo)
    r == 1 || return r
    b = takeresult(Int)
    b == 1 || return 0
    # get start
    ao = PyObject_GetAttrString(o, "start")
    isnull(ao) && return -1
    r = PyObject_TryConvert(ao, A)
    Py_DecRef(ao)
    r == 1 || return r
    a = takeresult(A)
    # get stop
    co = PyObject_GetAttrString(o, "stop")
    isnull(co) && return -1
    r = PyObject_TryConvert(co, A)
    Py_DecRef(co)
    r == 1 || return r
    c = takeresult(A)
    # success
    a′, c′ = promote(a, c - oftype(c, 1))
    putresult(tryconvert(S, UnitRange(a′, c′)))
end


text:
module TimeToLive

export TTL

using Base.Iterators: peel
using Dates: DateTime, Period, now

struct Node{T}
    value::T
    expiry::DateTime
end

isexpired(v::Node) = now() > v.expiry
isexpired(time::DateTime) = v::Node -> time > v.expiry

"""
    TTL(ttl::Period; refresh_on_access::Bool=true)
    TTL{K, V}(ttl::Period; refresh_on_access::Bool=true)

An associative [TTL](https://en.wikipedia.org/wiki/Time_to_live) cache.
If `refresh_on_access` is set, expiries are reset whenever they are accessed.
"""
struct TTL{K, V, P<:Period} <: AbstractDict{K, V}
    dict::Dict{K, Node{V}}
    ttl::P
    refresh::Bool

    TTL{K, V}(ttl::P; refresh_on_access::Bool=true) where {K, V, P <: Period} =
        new{K, V, P}(Dict{K, Node{V}}(), ttl, refresh_on_access)
    TTL(ttl::Period; refresh_on_access::Bool=true) =
        TTL{Any, Any}(ttl; refresh_on_access=refresh_on_access)
end

Base.delete!(t::TTL, key) = (delete!(t.dict, key); t)
Base.empty!(t::TTL) = (empty!(t.dict); t)
Base.get(f, t::TTL, key) = haskey(t, key) ? t[key] : f()
Base.get!(t::TTL, key, default) = haskey(t, key) ? t[key] : (t[key] = default)
Base.length(t::TTL) = count(!isexpired(now()), values(t.dict))
Base.push!(t::TTL, p::Pair) = (t[p.first] =  p.second; t)
Base.setindex!(t::TTL{K, V}, v, k) where {K, V} = t.dict[k] = Node{V}(v, now() + t.ttl)
Base.sizehint!(t::TTL, newsz) = (sizehint!(t.dict, newsz); t)

function Base.pop!(t::TTL)
    p = pop!(t.dict)
    return isexpired(p.second) ? pop!(t) : p.first => p.second.value
end

function Base.pop!(t::TTL, key)
    v = pop!(t.dict, key)
    isexpired(v) && throw(KeyError(key))
    return v.value
end

function Base.get(t::TTL, key, default)
    haskey(t.dict, key) || return default
    v = t.dict[key]
    if isexpired(v)
        delete!(t, key)
        return default
    end
    t.refresh && (t[key] = v.value)
    return v.value
end

function Base.getkey(t::TTL, key, default)
    return if haskey(t, key)
        if isexpired(t.dict[key])
            delete!(t, key)
            default
        else
            key
        end
    else
        default
    end
end

function Base.iterate(t::TTL, ks=keys(t.dict))
    isempty(ks) && return nothing
    k, rest = peel(ks)
    v = t.dict[k]
    return if isexpired(v)
        delete!(t, k)
        iterate(t, rest)
    else
        k => v.value, rest
    end
end

function Base.getindex(t::TTL, key)
    v = t.dict[key]
    if isexpired(v)
        delete!(t, key)
        throw(KeyError(key))
    elseif t.refresh
        t[key] = v.value
    end
    return v.value
end

end


text:
export match_attractors!, basin_entropy, basins_fractal_test

"""
    match_attractors!(b₋, a₋, b₊, a₊, [, method = :distance])
Attempt to match the attractors in basins/attractors `b₊, a₊` with those at `b₋, a₋`.
`b` is an array whose values encode the attractor ID, while `a` is a dictionary mapping
IDs to `Dataset`s containing the attractors (e.g. output of [`basins_of_attraction`](@ref)).
Typically the +,- mean after and before some change of parameter for a system.

In [`basins_of_attraction`](@ref) different attractors get assigned different IDs, however
which attractor gets which ID is somewhat arbitrary, and computing the basins of the
same system for slightly different parameters could label the "same" attractors (at
the different parameters) with different IDs. `match_attractors!` tries to "match" them
by modifying the attractor IDs.

The modification of IDs is always done on the `b, a` that have less attractors.

`method` decides the matching process:
* `method = :overlap` matches attractors whose basins before and after have the most
  overlap (in pixels).
* `method = :distance` matches attractors whose state space distance the smallest.
"""
function match_attractors!(b₋, a₋, b₊, a₊, method = :distance)
    @assert size(b₋) == size(b₊)
    if length(a₊) > length(a₋)
        # Set it up so that modification is always done on `+` attractors
        a₋, a₊ = a₊, a₋
        b₋, b₊ = b₊, b₋
    end
    ids₊, ids₋ = sort!(collect(keys(a₊))), sort!(collect(keys(a₋)))
    if method == :overlap
        match_metric = _match_from_overlaps(b₋, a₋, ids₋, b₊, a₊, ids₊)
    elseif method == :distance
        match_metric = _match_from_distance(b₋, a₋, ids₋, b₊, a₊, ids₊)
    else
        error("Unknown method")
    end

    replacement_mapping = Dict{Int, Int}()
    for (i, ι) in enumerate(ids₊)
        v = match_metric[i, :]
        for j in sortperm(v) # go through the match metric in sorted order
            if ids₋[j] ∈ values(replacement_mapping)
                continue # do not use keys that have been used
            else
                replacement_mapping[ι] = ids₋[j]
            end
        end
    end

    # Do the actual replacing
    replace!(b₊, replacement_mapping...)
    aorig = copy(a₊)
    for (k, v) ∈ replacement_mapping
        a₊[v] = aorig[k]
    end
    # delete unused keys
    for k ∈ keys(a₊)
        if k ∉ values(replacement_mapping); delete!(a₊, k); end
    end
    return
end

function _match_from_overlaps(b₋, a₋, ids₋, b₊, a₊, ids₊)
    # Compute normalized overlaps of each basin with each other basin
    overlaps = zeros(length(ids₊), length(ids₋))
    for (i, ι) in enumerate(ids₊)
        Bi = findall(isequal(ι), b₊)
        for (j, ξ) in enumerate(ids₋)
            Bj = findall(isequal(ξ), b₋)
            overlaps[i, j] = length(Bi ∩ Bj)/length(Bj)
        end
    end
    overlaps
end

using LinearAlgebra
function _match_from_distance(b₋, a₋, ids₋, b₊, a₊, ids₊)
    closeness = zeros(length(ids₊), length(ids₋))
    for (i, ι) in enumerate(ids₊)
        aι = a₊[ι]
        for (j, ξ) in enumerate(ids₋)
            aξ = a₋[ξ]
            closeness[i, j] = 1 / minimum(norm(x .- y) for x ∈ aι for y ∈ aξ)
        end
    end
    closeness
end

"""
    basin_entropy(basins, ε = 20) -> Sb, Sbb
This algorithm computes the basin entropy `Sb` of the basins of attraction. First, the input `basins`
is divided regularly into n-dimensional boxes of side `ε` (along all dimensions).
Then `Sb` is simply the average of the Gibbs entropy computed over these boxes. The
function returns the basin entropy `Sb` as well as the boundary basin entropy `Sbb`.
The later is the average of the entropy only for boxes that contains at least two
different basins, that is, for the boxes on the boundary.

The basin entropy is a measure of the uncertainty on the initial conditions of the basins.
It is maximum at the value `log(n_att)` being `n_att` the number of attractors. In
this case the boundary is intermingled: for a given initial condition we can find
another initial condition that lead to another basin arbitriraly close. It provides also
a simple criterion for fractality: if the boundary basin entropy `Sbb` is above `log(2)`
then we have a fractal boundary. It doesn't mean that basins with values below cannot
have a fractal boundary, for a more precise test see [`basins_fractal_test`](@ref). An important feature of the basin entropy is that it allows
comparisons between different basins using the same box size `ε`.

[^Daza2016]: A. Daza, A. Wagemakers, B. Georgeot, D. Guéry-Odelin and M. A. F. Sanjuán, Basin entropy: a new tool to analyze uncertainty in dynamical systems, Sci. Rep., 6, 31416, 2016.

"""
function basin_entropy(basins, ε = 20)
    dims = size(basins)
    vals = unique(basins)
    pn = zeros(length(vals))
    Sb = 0; Nb = 0; N = 0
    bx_tuple = ntuple(i -> range(1, dims[i] - rem(dims[i],ε), step = ε), length(dims))
    box_indices = CartesianIndices(bx_tuple)
    for box in box_indices
        # compute the range of indices for the current box
        I = CartesianIndices(ntuple(i -> range(box[i], box[i]+ε-1, step = 1), length(dims)))
        box_values = [basins[k] for k in I]
        N = N + 1
        Nb = Nb + (length(unique(box_values)) > 1)
        Sb = Sb + _box_entropy(box_values)
    end
    return Sb/N, Sb/Nb
end

function _box_entropy(box_values)
    h = 0.
    for (k,v) in enumerate(unique(box_values))
        p = count( x -> (x == v), box_values)/length(box_values)
        h += p*log(1/p)
    end
    return h
end

"""
    basins_fractal_test(basins; ε = 20, Ntotal = 1000) -> test_res, Sbb
This is an automated test to decide if the boundary of the basins has fractal structures.
The bottom line is to look at the basins with a magnifier of size `ε` at random in `basins`.
If what we see in the magnifier looks like a smooth boundary (in average) we decide that
the boundary is smooth. If it is not smooth we can say that at the scale `ε` we have
structures, i.e., it is fractal.

In practice the algorithm computes the boundary basin entropy `Sbb` [`basin_entropy`](@ref) for `Ntotal`
random balls of radius `ε`. If the computed value is equal to theoretical value of a smooth boundary
(taking into account statistical errors and biases) then we decide that we have a smooth
boundary. Notice that the response `test_res` may depend on the chosen ball radius `ε`. For larger size,
we may observe structures for smooth boundary and we obtain a *different* answer.

The output `test_res` is a symbol describing the nature of the basin and the output `Sbb` is
the estimated value of the boundary basin entropy with the sampling method.

[^Puy2021] Andreu Puy, Alvar Daza, Alexandre Wagemakers, Miguel A. F. Sanjuán. A test for fractal boundaries based on the basin entropy. Commun Nonlinear Sci Numer Simulat, 95, 105588, 2021.

## Keyword arguments
* `ε = 20`: size of the ball for the test of basin. The result of the test may change with the size.
* `Ntotal = 1000`: number of balls to test in the boundary for the computation of `Sbb`
"""
function basins_fractal_test(basins; ε = 20, Ntotal = 1000)
    dims = size(basins)
    vals = unique(basins)
    S=Int(length(vals))
    pn=zeros(Float64,1,S)
    # Sanity check.
    if minimum(dims)/ε < 50
        @warn "Maybe the size of the grid is not fine enough."
    end
    if Ntotal < 100
        error("Ntotal must be larger than 1000 to gather enough statitics.")
    end

    v_pts = zeros(Float64, length(dims), prod(dims))
    I = CartesianIndices(basins)
    for (k,coord) in enumerate(I)
         v_pts[:, k] = [Tuple(coord)...]
    end
    tree = searchstructure(KDTree, v_pts, Euclidean())
    # Now get the values in the boxes.
    Nb = 1; N = 1; Sb = 0;
    N_stat = zeros(Ntotal)
    while Nb < Ntotal
        p = [rand()*(sz-ε)+ε for sz in dims]
        idxs = isearch(tree, p, WithinRange(ε))
        box_values = basins[idxs]
        bx_ent = _box_entropy(box_values)
        if bx_ent > 0
            Nb = Nb + 1
            N_stat[Nb] = bx_ent
        end
    end

    Ŝbb = mean(N_stat)
    σ_sbb = std(N_stat)/sqrt(Nb)    # Table of boundary basin entropy of a smooth boundary for dimension 1 to 5:
    Sbb_tab = [0.499999, 0.4395093, 0.39609176, 0.36319428, 0.33722572]
    if length(dims) ≤ 5
        Sbb_s = Sbb_tab[length(dims)]
    else
        Sbb_s = 0.898*length(dims)^-0.4995
    end
    # Systematic error aproximation for the disk of radius ε
    δub = 0.224*ε^-1.006

    tst_res = :smooth
    if Ŝbb < (Sbb_s - σ_sbb) ||  Ŝbb > (σ_sbb + Sbb_s + δub)
        println("Fractal boundary for size of box ε=", ε)
        tst_res = :fractal
    else
        println("Smooth boundary for size of box ε=", ε)
        tst_res = :smooth
    end

    return tst_res, Ŝbb
end


text:
struct CyclicContainer <: AbstractVector{String}
    c::Vector{String}
    n::Int
end
CyclicContainer(c) = CyclicContainer(c, 0)

Base.length(c::CyclicContainer) = typemax(Int)
Base.size(c::CyclicContainer) = size(c.c)
Base.iterate(c::CyclicContainer, state=1) = Base.iterate(c.c, state)
Base.getindex(c::CyclicContainer, i) = c.c[(i-1)%length(c.c) + 1]
function Base.getindex(c::CyclicContainer)
    c.n += 1
    c[c.n]
end
Base.iterate(c::CyclicContainer, i = 1) = iterate(c.c, i)

COLORSCHEME = [
   "#233B43",
   "#499cbf",
   "#E84646",
   "#168e32",
   "#C29365",
   "#985CC9",
   "#8f8f8f"
]
COLORS = CyclicContainer(COLORSCHEME)

max_stars_repo_name: moritz-g/ExercisesRepo

max_issues_repo_name: moritz-g/ExercisesRepo

max_forks_repo_name: moritz-g/ExercisesRepo


text:
"""
    mutation:classic(A::AbstractMatrix, lower_limits::AbstractArray, upper_limits::AbstractArray, F::Real)
Given a matrix `A` with `lower_limits` and `upper_limits` arrays, crates a new mutated
matrix with a constant sacale factor `F`.

# Parameters
**`A::AbstractMatrix`**:

Input matrix

## **`lower_limits::AbstractArray`**:

Array that contains all the minimun values
that each individue could obtain.

## **`upper_limits::AbstractArray`**:

Array that contains all the maximum values
that each individue could obtain.

## **`F::Real`**:

The scale factor, F ∈ (0,1+), is a positive real number that controls
the rate at which the population evolves. While there is no upper limit on F,
effective values are seldom greater than 1.0.
(*Price, K., Storn, R. M., & Lampinen, J. A. (2006).
Differential evolution: a practical approach to global optimization.
Springer Science & Business Media.*)

# Examples
```julia-repl
A = 5 .*rand(5,3)
5×3 Matrix{Float64}:
 1.18384  0.587246  4.46021
 4.58842  3.52444   1.39847
 1.08242  4.8591    4.9314
 3.54709  3.17776   4.42353
 4.62508  4.84452   4.59266
```
`A` is a matrix with minimun and maximum possible values equal to 0 and 5 respectively.
```julia-repl
julia> mutation_classic(A,[0,0,0],[5,5,5],1.3)
5×3 Matrix{Float64}:
 4.53165   4.40841   2.46325
 1.55285   1.47686   4.64034
 3.72591   0.302261  1.29996
 1.2315    2.30335   0.0760031
 0.797949  1.3387    0.738242
```
"""
function mutation_classic(A::AbstractMatrix, lower_limits::AbstractArray, upper_limits::AbstractArray, F::Real)

    mutated = similar(A)
    population_size = size(A,1)
    number_of_parameters = size(A,2)

    @simd for individue in 1:population_size

        #Obtaining 3 differents individues without taking into account the current individue

        rnd_individues = rand_exclusive(deleteat!(collect(1:population_size),individue),3)

        @simd for parameter in 1:number_of_parameters
            @inbounds mutated[individue,parameter] = A[rnd_individues[1],parameter] + F*(A[rnd_individues[2],parameter]-A[rnd_individues[3],parameter])
            if (mutated[individue,parameter] < lower_limits[parameter]) | (mutated[individue,parameter] > upper_limits[parameter])
                @inbounds mutated[individue,parameter] = (upper_limits[parameter] - lower_limits[parameter])*rand() + lower_limits[parameter]
            end
        end
    end

    return mutated

end

"""
    mutation_jither(A::AbstractMatrix, lower_limits::AbstractArray, upper_limits::AbstractArray, F::Real)
Similar to **`mutation_classic`**. Given a population matrix `A`, perform the mutation with a random scale factor
between (0,1]. Each scale factor is diferent for each parameter.
"""
function mutation_jither(A::AbstractMatrix, lower_limits::AbstractArray, upper_limits::AbstractArray, F::Real)

    mutated = similar(A)
    population_size = size(A,1)
    number_of_parameters = size(A,2)

    @simd for individue in 1:population_size

        #Obtaining 3 differents individues without taking into account the current individue

        rnd_individues = rand_exclusive(deleteat!(collect(1:population_size),individue),3)

        @simd for parameter in 1:number_of_parameters
            @inbounds mutated[individue,parameter] = A[rnd_individues[1],parameter] + rand()*F*(A[rnd_individues[2],parameter]-A[rnd_individues[3],parameter])
            if (mutated[individue,parameter] < lower_limits[parameter]) | (mutated[individue,parameter] > upper_limits[parameter])
                @inbounds mutated[individue,parameter] = (upper_limits[parameter] - lower_limits[parameter])*rand() + lower_limits[parameter]
            end
        end
    end

    return mutated
end

"""
    mutation_dither(A::AbstractMatrix, lower_limits::AbstractArray, upper_limits::AbstractArray, F::Real)
Similar to **`mutation_classic`**. Given a population matrix `A`, perforn the mutation with a random scale factor
in the interval (0,F). Each scale factor is diferent for each individue but the same for all parameters.
"""
function mutation_dither(A::AbstractMatrix, lower_limits::AbstractArray, upper_limits::AbstractArray, F::Real)

    mutated = similar(A)
    population_size = size(A,1)
    number_of_parameters = size(A,2)

    @simd for individue in 1:population_size

        #Obtaining 3 differents individues without taking into account the current individue

        rnd_individues = rand_exclusive(deleteat!(collect(1:population_size),individue),3)
        rnd = rand()
        @simd for parameter in 1:number_of_parameters
            @inbounds mutated[individue,parameter] = A[rnd_individues[1],parameter] + rnd*F*(A[rnd_individues[2],parameter]-A[rnd_individues[3],parameter])
            if (mutated[individue,parameter] < lower_limits[parameter]) | (mutated[individue,parameter] > upper_limits[parameter])
                @inbounds mutated[individue,parameter] = (upper_limits[parameter] - lower_limits[parameter])*rand() + lower_limits[parameter]
            end
        end
    end

    return mutated
end

"""
    mutation_JADE_no_archive(A::AbstractMatrix, lower_limits::AbstractArray, upper_limits::AbstractArray, F::Real)
Named DE/current-to-pbest/1

"""
function mutation_current2pbest(xi::AbstractArray, x_best::AbstractArray, x_r1::AbstractArray, x_r2::AbstractArray, F::Real)
    v = similar(xi)
    @inbounds for parameter in 1:length(xi)
        v[parameter] = xi[parameter] + F*(x_best[parameter] - xi[parameter]) + F*(x_r1[parameter] - x_r2[parameter])
    end

    return v
end


text:
function apply_drawing!(g::GLE, scatter3::Scatter3D, el_cntr::Int,
                        figid::String, axidx::Int)

    faux = auxpath(hash(scatter3.data), figid, axidx)
    isfile(faux) || csv_writer(faux, scatter3.data, false)

    # find the figure and axes
    fig = Figure(figid; _noreset=true)
    ax  = fig.axes[axidx]
    # retrieve limits, which are needed to get the scale right
    xmin  = ax.xaxis.min
    xspan = ax.xaxis.max - xmin
    ymin  = ax.yaxis.min
    yspan = ax.yaxis.max - ymin

    # add the GLE sub (if not already there)
    add_sub_plot3!(fig)

    ls = scatter3.linestyle
    if !isdef(scatter3.linestyle.color)
        cc = mod(el_cntr, GP_ENV["SIZE_PALETTE"])
        (cc == 0) && (cc = GP_ENV["SIZE_PALETTE"])
        ls.color = GP_ENV["PALETTE"][cc]
    end

    # apply linestyle
    "\ngsave"    |> g
    apply_linestyle!(g, ls; nosmooth=true, addset=true)
    showline = Int(ls.lstyle != -1)

    "\nplot3 \"$faux\" $xmin $xspan $ymin $yspan $showline" |> g

    # apply markerstyle
    ms = scatter3.markerstyle
    if isanydef(ms)
        "1"  |> g # showmarker
        # fill default values
        isdef(ms.marker) || (ms.marker = "fcircle")
        isdef(ms.color)  || (ms.color = ls.color)
        isdef(ms.msize)  || (ms.msize = 0.1)
        if (ms.color != ls.color)
            add_sub_marker!(fig, ms)
            str(ms) |> g
            # see also boxplot for an explanation of the scaling
            "$(2*ms.msize/fig.textstyle.hei)" |> g
        else
            ms.marker |> g
            "$(2*ms.msize)" |> g
        end
    else
        # don't show marker, default fields (won't be read)
        "-1 xxx 0" |> g
    end
    # end of scatter3d, restore style
    "\ngrestore" |> g

    return el_cntr + 1
end

#=
NOTE

* need to think about how to push and retrieve axes, they MUST be present otherwise
the whole thing is badly scaled.

any call --> update axes lims
assembling --> call axes lims

=#


text:
# Autogenerated wrapper script for tectonic_jll for i686-linux-musl-cxx03
export tectonic

using Fontconfig_jll
using FreeType2_jll
using Graphite2_jll
using HarfBuzz_jll
using HarfBuzz_ICU_jll
using ICU_jll
using OpenSSL_jll
using Zlib_jll
using libpng_jll
JLLWrappers.@generate_wrapper_header("tectonic")
JLLWrappers.@declare_executable_product(tectonic)
function __init__()
    JLLWrappers.@generate_init_header(Fontconfig_jll, FreeType2_jll, Graphite2_jll, HarfBuzz_jll, HarfBuzz_ICU_jll, ICU_jll, OpenSSL_jll, Zlib_jll, libpng_jll)
    JLLWrappers.@init_executable_product(
        tectonic,
        "bin/tectonic",
    )

    JLLWrappers.@generate_init_footer()
end  # __init__()


text:
module Examples_sep
using LinearAlgebra
using Random

srcDir = dirname(@__FILE__)*"\\"
include(srcDir *"Utils_states.jl")
using .Utils_states

export get_sep_examples

## Separable States
"""
Separable state examples:
"""
function get_sep_example()
    ρ = Dict()
##
    ρ["Ex3-8DNY20"]   = get_ρ_DNY20()

##  CD12
    ρ["T1CD12i"]    = get_ρ_T1CD12(1)
    ρ["T1CD12ii"]   = get_ρ_T1CD12(2)
    # ρ["T1CD12iii"]  = get_ρ_T1CD12(3)

    ρ["T2CD12i"]    = get_ρ_T2CD12(1)
    ρ["T2CD12ii"]   = get_ρ_T2CD12(2)
    # ρ["T2CD12iii"]  = get_ρ_T2CD12(3)
    # ρ["T2CD12iv"]  = get_ρ_T2CD12(4)
    ρ["T2CD12v"]    = get_ρ_T2CD12(5)

    # ρ["Ex26CD12"]   = get_ρ_Ex26CD12() # Wrong shape

## Random batch
 ## Real
    ρ["RANDa1"]  = gen_ρ_RAND(3, 5)
    ρ["RANDa2"]  = gen_ρ_RAND(3, 10)
    ρ["RANDa3"]  = gen_ρ_RAND(3, 15)
    ρ["RANDa4"]  = gen_ρ_RAND(3, 20)
    ρ["RANDa5"]  = gen_ρ_RAND(3, 25)

    ρ["RANDb1"]  = gen_ρ_RAND(4, 5)
    ρ["RANDb2"]  = gen_ρ_RAND(4, 10)
    ρ["RANDb3"]  = gen_ρ_RAND(4, 15)
    ρ["RANDb4"]  = gen_ρ_RAND(4, 20)
    ρ["RANDb5"]  = gen_ρ_RAND(4, 25)
 ## Complex
    ρ["RANDℂa1"]  = gen_ρ_RAND(3,  5,true)
    ρ["RANDℂa2"]  = gen_ρ_RAND(3, 10,true)
    ρ["RANDℂa3"]  = gen_ρ_RAND(3, 15,true)
    ρ["RANDℂa4"]  = gen_ρ_RAND(3, 20,true)
    ρ["RANDℂa5"]  = gen_ρ_RAND(3, 25,true)

    ρ["RANDℂb1"]  = gen_ρ_RAND(4,  5,true)
    ρ["RANDℂb2"]  = gen_ρ_RAND(4, 10,true)
    ρ["RANDℂb3"]  = gen_ρ_RAND(4, 15,true)
    ρ["RANDℂb4"]  = gen_ρ_RAND(4, 20,true)
    ρ["RANDℂb5"]  = gen_ρ_RAND(4, 25,true)

    for key in keys(ρ)
        ρ[key] = Utils_states.maketraceone(ρ[key])
    end
    return ρ
end

## Examples
"""generates a matrix of the form ∑ʳaᵀa⊗bᵀb, with a,b ∈ uniform random entry-wise.  d ∈ {2,3,4} , r ∈ [9]"""
function gen_ρ_RAND(d::Integer, r::Integer, isℂ = false)
    seed = 343
    Random.seed!(seed)
    if isℂ
        a = [randn(1,d) + im*randn(1,d) for i in 1:r ]
        b = [randn(1,d) + im*randn(1,d) for i in 1:r ]
    else
        a = [randn(1,d) for i in 1:r ]
        b = [randn(1,d) for i in 1:r ]
    end
    ρ = zeros(d^2,d^2)
    for i in 1:r
        A = transpose(a[i])*conj(a[i])
        B = transpose(b[i])*conj(b[i])
        ρ = kron(A,B) + ρ
    end
    return ρ
end

""" http://arxiv.org/abs/1210.0111v2"""
function get_ρ_T1CD12(i)
    ρ = Dict()
    # |00><00| + |11><11|
    ρ[1] =  psep(1,1,2) + psep(2,2,2)

    # |00><00| + |11><11| + (|0> + |1>)⊗(|0> + |1>)(<0| + <1|)⊗(<0| + <1|)
    ρ[2] = psep(1,1,2) + psep(2,2,2) + sq(ψ(2))

    return ρ[i]
end

""" http://arxiv.org/abs/1210.0111v2"""
function get_ρ_T2CD12(i)
    ρ = Dict()

    # |00><00| + |11><11| + |12><12|
    ρ[1] = psep(1,1,3) + psep(2,2,3) + psep(2,3,3)

    # |00><00| + |01><01| + |11><11| + |12><12|
    ρ[2] = psep(1,1,3) + psep(1,2,3) + psep(2,2,3) + psep(2,3,3)

    # |00><00| + |01><01| + |02><02| + |11><11| + |12><12|
    ρ[5] = psep(1,1,3) + psep(1,2,3) +  psep(1,3,3) + psep(2,2,3) + psep(2,3,3)

    return ρ[i]
end

""" http://arxiv.org/abs/1210.0111v2"""
function get_ρ_Ex26CD12()

    ρ = [2 0 0 0 0 0
         0 4 2 0 0 2
         0 2 2 1 -1 0
         0 0 1 2 1 -1
         0 0 -1 1 5 1
         0 2 0 0 1 2]

    return ρ
end

"""https://arxiv.org/abs/2011.08132v1"""
function get_ρ_DNY20()
    H_flat = [i₁*j₁ + i₂*j₂ for i₁ in 1:3 for i₂ in 1:3 for j₁ in 1:3   for j₂ in 1:3 ]
    return  reshape(H_flat,9,9)
end

# """ http://arxiv.org/abs/quant-ph/9605038v2 """
# function get_ρ_HHH1(p  = 0.5)
#     # Random.seed!(343)
#     a  = 0.5; b = 0.8; # arbitary possitive numbers
#     ψ₁ = a*Utils_states.ψ(2,2,2) + b*Utils_states.ψ(1,1,2)
#     ψ₂ = a*Utils_states.ψ(2,1,2) + b*Utils_states.ψ(1,2,2)
#     ρ_temp = p*Utils_states.sq(ψ₁) + (1 - p)*Utils_states.sq(ψ₂)
#     return ρ_temp
# end

end

max_stars_repo_name: JAndriesJ/sep-rank-bounding

max_issues_repo_name: JAndriesJ/sep-rank-bounding

max_forks_repo_name: JAndriesJ/sep-rank-bounding


text:
# """
#     vertex_shape(shape::Symbol, clip::Bool; <keyword arguments>)

# Create an outline for the graph vertex. Also, can define a clipping region for the vertex region.

# Returns a dictionary of drawing options and a callable draw function (in this order).

# # Arguments
# - `shape::Symbol`: Specify a vertex shape among `:circle`, `:rectangle` or a `:polygon` (to be implemented).
# - `clip::Bool`: Define a clipping region over the region defined by `shape`.

# # Keywords
# - `radius`: Specify if shape is `:circle`.
# - `width`: Specify if shape is `:rectangle`.
# - `height`: Specify if shape is `:rectangle`.
# """
function vertex_shape(shape::Symbol=:circle, clip::Bool=false; dimensions=())
    if !(dimensions isa Tuple)
        dimensions = Tuple(dimensions)
    end
    dimensions = Tuple(dimensions)
    if shape == :rectangle && length(dimensions) != 2
        throw(ArgumentError("Rectangle must have 2 dimensions (width, height) provided for vertex shape."))
    elseif shape == :circle && length(dimensions) != 1
        throw(ArgumentError("Circle must have 1 dimension (radius) provided for vertex shape."))
    end
    draw = (video, object, frame; shape=shape, dimensions=dimensions, clip=clip, position, kwargs...) -> begin
        Luxor.newpath()
        if shape == :rectangle
            w, h = dimensions
            rect(position[1]-w/2, position[2]-h/2, w, h, :path)
            bounding_box = (position-(w/2, h/2), position+(w/2, h/2))
        elseif shape == :circle
            r = dimensions[1]
            circle(position, r, :path)
            bounding_box = (position-r, position+r)
        end
        if clip
            clippreserve()
        end
        outline = first(pathtopoly())
        strokepath()
        Luxor.closepath()
        @register_style_opts object shape dimensions clip bounding_box outline
    end
    return :vertex_shape=>draw
end

function vertex_text_style(; color="black", fonttype="Times Roman", fontsize=10, opacity=0.8)
    draw = (video, object, frame; text_color=color, fonttype=fonttype, fontsize=fontsize, text_opacity=opacity, kwargs...) -> begin
        sethue(text_color)
        setfont(fonttype, fontsize)
        setopacity(text_opacity)
        @register_style_opts object text_color fonttype fontsize text_opacity
    end
    return :vertex_text_style=>draw
end

# """
#     vertex_text(text::AbstractString, offset::Point, color)
#     vertex_text(text::LaTeXString, offset::Point, color)

# Label a vertex with text (or latex), which is also rotated to match the direction specified by `offset`.

# # Arguments
# - `offset::Point`: Specify the offset of the text label with respect to the vertex center.
#     - The text is translated and rotated in the direction of the offset.
# - `color`: The color of the text. Default is "black".
# - `shift::Real`: Indicates the shift w.r.t the offset direction.
#     - Eg. If the offset direction is in the upper or lower left quadrant calculated shift is (`shift`, 0).
#     - Default shift is 1.
# """

function vertex_text(text_content, align::Symbol; shift::Real=10, angle::Float64=0.0, fit_to_bbox=false)
    outside = Tuple(normalize((rand(1, 2))))
    find_shift = (video, object, frame; text_align=align, shift=shift, bounding_box, kwargs...) -> begin
        text_box = bounding_box
        text_dims = last(text_box)-first(text_box)
        if align == :top
            text_shift = (0, -text_dims[2]/2-shift)
        elseif align == :bottom
            text_shift = (0, text_dims[2]/2+shift)
        elseif align == :left
            text_shift = (-text_dims[1]/2-shift, 0)
        elseif align == :right
            text_shift = (text_dims[1]/2+shift, 0)
        elseif align == :inside
            text_shift = O
        elseif align == :outside
            text_shift = outside .* shift
        end
        @register_style_opts object text_shift text_align shift fit_to_bbox
        return text_shift
    end

    _, draw = vertex_text(text_content; angle=angle, fit_to_bbox=fit_to_bbox)

    return :vertex_text=>((args...; text_shift=(O, O), kwargs...) ->
        begin
            text_shift = find_shift(args...; kwargs...);
            draw(args...; text_shift=text_shift, kwargs...)
        end)
end

# """
#     vertex_text(text::AbstractString, align::Symbol, color, offset::Real)
#     vertex_text(text::LaTeXString, align::Symbol, color, offset::Real)

# Label a vertex with text or latex.

# # Arguments
# - `align::Symbol`: Specify the alignment of the text label with respect to the vertex.
#     - This position is determined with respect to the vertex `bounding_box` returned as a drawing option by [`vertex_shape`](@ref).
#     - Can take the values `:inside`, `:top`, `:bottom`, `:left`, `:right`. Default is `:inside`.
# - `color`: The color of the text. Default is "black".
# - `shift`: Used only when `align` is not `:inside`. Specifies an offset in the direction of `align`.
# """
function vertex_text(text_content; shift=(0, 0), angle::Float64=0.0, fit_to_bbox=false)
    draw = (video, object, frame; text_content=text_content, text_shift=shift, text_angle=angle, clip, bounding_box, kwargs...) -> begin
        kw = Dict{Symbol, Any}(collect(kwargs)...)
        text_align = get(kw, :text_align, :inside)
        fontsize = get(kw, :fontsize, 10)
        # Text box is inistally derived from vertex bounding box
        text_box = bounding_box
        if text_content isa LaTeXString
            svg = get_latex_svg(text_content)
            w, h = svgwh(svg)
        elseif text_content isa AbstractString
            ext = textextents(text_content)
            w, h = ext[3], ext[4]
        end
        text_dims = Tuple(last(text_box)-first(text_box))
        text_marker = (text_box[1]+text_box[2])/2
        scl = Point(1, 1)
        if fit_to_bbox
            # 1.5 is chosen after some visual inspections
            scl = min(text_dims[1]/w, text_dims[2]/h)/1.5 .* (1, 1)
        end
        if text_align == :inside
            text_marker_align = (:center, :middle)
        else
            # ToDo: throw warning - Clip is ignored since text outside bounding box
            text_marker_shift, text_marker_align = _vertex_text_shift(text_box, text_shift, text_align)
            text_marker += text_marker_shift
        end
        scale(scl...)
        object.current_setting.fontsize = 60
        if text_content isa LaTeXString
            translate(text_marker/Tuple(scl))
            # Change to radians and negate to match rotate's convention
            rotate(-degreetoradians(angle))
            # ToDo: Can we pass fontsize directly into latex?
            _fontsize = object.current_setting.fontsize
            object.current_setting.fontsize = fontsize
            latex(text_content, O, text_marker_align[2], text_marker_align[1])
            object.current_setting.fontsize = _fontsize

            rotate(degreetoradians(angle))
            translate(-text_marker/Tuple(scl))
        elseif text_content isa AbstractString
            text(text_content, text_marker/Tuple(scl), halign = text_marker_align[1], valign = text_marker_align[2], angle=angle)
        else
        end
        scale(1/scl[1], 1/scl[2])
        @register_style_opts object text_content text_box text_shift text_angle
    end
    return :vertex_text=>draw
end

function _vertex_text_shift(text_box, shift, align)
    text_box_center = (text_box[1] + text_box[2])/2
    top_left = text_box[1] - text_box_center
    top_right = Point(text_box[2][1], text_box[1][2]) - text_box_center
    bottom_left = Point(text_box[1][1], text_box[2][2]) - text_box_center
    bottom_right = text_box[2] - text_box_center
    shift = Point(shift)
    if intersection(top_left, top_right, O, shift)[1]
        text_align = (:center, :bottom)
        point1, point2 = top_left, top_right
    elseif intersection(top_right, bottom_right, O, shift)[1]
        text_align = (:left, :middle)
        point1, point2 = top_right, bottom_right
    elseif intersection(bottom_right, bottom_left, O, shift)[1]
        text_align = (:center, :top)
        point1, point2 = bottom_right, bottom_left
    elseif intersection(bottom_left, top_left, O, shift)[1]
        text_align = (:right, :middle)
        point1, point2 = bottom_left, top_left
    end
    intersects, point = intersection(text_box_center, text_box_center+shift, point1, point2)
    if !intersects && align == :outside
        # ToDo: throw warning - inside sector but no intersection of lines was found
        text_marker_shift = (norm(text_box_center-point) + norm(shift)) * shift./norm(shift)
    else
        text_marker_shift = shift
    end
    return text_marker_shift, text_align
end

# """
#     vertex_fill(fill_type::Symbol, fill_with::String)

# Fill in the interior of the vertex with an image or a color.

# # Arguments
# - `fill_type::Symbol`: Can be `image` or `color`.
# - `fill_with`: Can be a color like "red" or a image path in the same directory like "image.png".
# """
function vertex_fill(type::Symbol, with::String, opacity::Float64=0.5)
    draw = (video, object, frame; fill_type=type, fill_with=with, fill_opacity=opacity, bounding_box, kwargs...) -> begin
        kw = Dict{Symbol, Any}(collect(kwargs)...)
        outline = get(kw, :outline, nothing)
        if fill_type == :image
            img = readpng(fill_with)
            image_dims = last(bounding_box)-first(bounding_box)
            scl = min(image_dims[1]/img.width, image_dims[2]/img.height) .* (1, 1)
            scale(scl...)
            placeimage(img, bounding_box[1]/Tuple(scl), fill_opacity)
            scale(1/scl[1], 1/scl[2])
        elseif fill_type == :color
            setopacity(fill_opacity)
            sethue(fill_with)
            fill_dims = last(bounding_box)-first(bounding_box)
            if outline !== nothing
                Luxor.poly([outline..., first(outline)], :fill)
            else
                circle((bounding_box[1]+bounding_box[2])/2, max(fill_dims...)/2, :fill)
            end
        end
        @register_style_opts object fill_type fill_with fill_opacity
    end
    return :vertex_fill=>draw
end

# """
#     vertex_border(color::String, border_width::Real)

# Create a border around the vertex shape.

# If the `:shape` is not specified, the `bounding_box` becomes the border.

# # Arguments
# - `color`: The color of the text. Default is "black".
# - `border_width::Real`: The line width used for the border. Defualt is 3.
# """
function vertex_border(color="black", width::Real=3, opacity=0.7)
    draw = (video, object, frame; border_color=color, border_width=width, border_opacity=opacity, clip, position, shape, kwargs...) -> begin
        kw = Dict{Symbol, Any}(collect(kwargs)...)
        setline(border_width)
        sethue(border_color)
        setopacity(border_opacity)
        dims = get(kw, :dimensions, 5)
        outline = get(kw, :outline, nothing)
        if outline !== nothing
            Luxor.poly([outline..., first(outline)], :stroke)
        elseif shape == :circle
            r = dims[1]
            circle(position, r, :stroke)
        elseif shape == :rectangle
            w = dims[1]
            h = dims[2]
            rect(position[1]-w/2, position[2]-h/2, w, h, :stroke)
        else
            bounding_box = get(kw, :bounding_box, (O, O))
            rect(bounding_box[1], (bounding_box[2]-bounding_box[1])..., :stroke)
        end
        @register_style_opts object border_color border_width border_opacity
    end
    return :vertex_border=>draw
end


text:
# This is basically maple/lda_x.mpl from libxc
#
# LDA Slater exchange (DOI: 10.1017/S0305004100016108 and 10.1007/BF01340281)

α = symbols("α")

lda_x_ax = -α*RS_FACTOR*X_FACTOR_C/2^(4//3)

f_lda_x(rs, z) = lda_x_ax*((1 + z)^(4//3) + (1 - z)^(4//3))/rs
f(rs, z, junk...) = f_lda_x(rs, z)