I suggest you always approach your data "top-down" rather than try to deal with siblings.
Below is a solution:
t:\ftemp>type boolean1.xml
<event>
<criteria>
<and>A</and>
<and>B</and>
<and>
<or>
<and>C</and>
<and>D</and>
</or>
<or>E</or>
</and>
</criteria>
</event>
t:\ftemp>call xslt2 boolean1.xml boolean1.xsl
To meet the criteria, event must have A and B and either C and D or E
t:\ftemp>type boolean1.xsl
<?xml version="1.0" encoding="US-ASCII"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:output method="text"/>
<!--eat white-space-->
<xsl:template match="text()[not(normalize-space())]"/>
<!--start result-->
<xsl:template match="event">
To meet the criteria, event must have<xsl:apply-templates/>
</xsl:template>
<!--handle conjunction-->
<xsl:template match="*[child::and]">
<xsl:for-each select="child::and">
<xsl:if test="position()>1"> and</xsl:if>
<xsl:text> </xsl:text>
<xsl:apply-templates select="."/>
</xsl:for-each>
</xsl:template>
<!--handle alternation-->
<xsl:template match="*[child::or]">
<xsl:for-each select="child::or">
<xsl:if test="position()>1"> or</xsl:if>
<xsl:text> </xsl:text>
<xsl:apply-templates select="."/>
</xsl:for-each>
</xsl:template>
<!--special grammar case for alternation between 2 operands-->
<xsl:template match="*[count(child::or) = 2]" priority="1">
<xsl:text> either</xsl:text>
<xsl:next-match/>
</xsl:template>
<!--don't allow a mixture-->
<xsl:template match="*[child::and and child::or]" priority="2">
<xsl:message terminate="yes">
<xsl:text>A mixture of ands and ors is not allowed.</xsl:text>
</xsl:message>
</xsl:template>
</xsl:stylesheet>
t:\ftemp>rem Done!
As for suggestions for changing your XML, I suggest using a structure that doesn't allow for unexpected combinations, such as "what to do when both ands and ors are siblings". Consider the following:
t:\ftemp>type boolean2.xml
<event>
<criteria>
<and>
<op>A</op>
<op>B</op>
<or>
<and>
<op>C</op>
<op>D</op>
</and>
<op>E</op>
</or>
</and>
</criteria>
</event>
t:\ftemp>call xslt2 boolean2.xml boolean2.xsl
To meet the criteria, event must have A and B and either C and D or E
t:\ftemp>type boolean2.xsl
<?xml version="1.0" encoding="US-ASCII"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:output method="text"/>
<!--eat white-space-->
<xsl:template match="text()[not(normalize-space())]"/>
<!--start result-->
<xsl:template match="event">
To meet the criteria, event must have<xsl:apply-templates/>
</xsl:template>
<!--handle conjunction-->
<xsl:template match="and">
<xsl:for-each select="*">
<xsl:if test="position()>1"> and</xsl:if>
<xsl:text> </xsl:text>
<xsl:apply-templates select="."/>
</xsl:for-each>
</xsl:template>
<!--handle alternation-->
<xsl:template match="or">
<xsl:for-each select="*">
<xsl:if test="position()>1"> or</xsl:if>
<xsl:text> </xsl:text>
<xsl:apply-templates select="."/>
</xsl:for-each>
</xsl:template>
<!--special grammar case for alternation between 2 operands-->
<xsl:template match="or[count(*) = 2]" priority="1">
<xsl:text> either</xsl:text>
<xsl:next-match/>
</xsl:template>
</xsl:stylesheet>
t:\ftemp>rem Done!
In this second approach, the "action" is triggered by the element, not by the children operand elements. I think this would be more direct.
Note that for the English reader there may be some grammatical challenges when nesting ands and ors deeply without some punctuation somewhere.