diff --git a/lib/baseline_report.xsl b/lib/baseline_report.xsl
new file mode 100644
index 0000000..d27e703
--- /dev/null
+++ b/lib/baseline_report.xsl
@@ -0,0 +1,474 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+	Copyright 2018 Internet2
+
+	Licensed under the Apache License, Version 2.0 (the "License");
+	you may not use this file except in compliance with the License.
+	You may obtain a copy of the License at
+
+		http://www.apache.org/licenses/LICENSE-2.0
+
+	Unless required by applicable law or agreed to in writing, software
+	distributed under the License is distributed on an "AS IS" BASIS,
+	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+	See the License for the specific language governing permissions and
+	limitations under the License.
+-->
+<!--
+	baseline_report_csv.xsl
+
+	This XSL transform takes a SAML metadata aggregate and produces a list of
+	IdP and SP baseline compliance factors (documented at [1]) for entities registered by 'https://incommon.org', containing the following fields:
+
+IdPs:
+      1. entityID
+      2. timestamp
+      3. Organization Display Name
+      4. "IdP" string literal
+	  5. IdP Display Name: mdui:DisplayName
+	  6. IdP Privacy URL or NONE
+	  7. IdP Logo present TRUE/FALSE
+	  8. IdP Tech contact or NONE
+	  9. IdP Admin contact or NONE
+	  10. IdP Security contact or NONE
+	  11. IdP complies with baseline expectations "Y" or "N"
+
+	The IdP Display Name field is set to "NONE" if neither mdui:DisplayName nor
+	md:OrganizationDisplayName exist in the entity descriptor.
+
+	BUG: Incorrectly processes an entity with multiple IDPSSODescriptor elements
+
+SPs:
+      1. entityID
+      2. timestamp
+      3. Organization Display Name
+      4. "SP" string literal
+	  5. SP Display Name: mdui:DisplayName
+	  6. SP Privacy URL or NONE
+	  7. SP Logo present TRUE/FALSE
+	  8. SP Tech contact or NONE
+	  9. SP Admin contact or NONE
+	  10. SP Security contact or NONE
+	  11. SP complies with baseline expectations "Y" or "N"
+
+	The SP Display Name field is set to "NONE" if neither mdui:DisplayName nor
+	md:OrganizationDisplayName exist in the entity descriptor.
+
+	BUG: Incorrectly processes an entity with multiple SPSSODescriptor elements
+
+[1] https://spaces.internet2.edu/x/4RL9Bg
+
+-->
+<xsl:stylesheet version="1.0"
+	xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+	xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata"
+	xmlns:mdattr="urn:oasis:names:tc:SAML:metadata:attribute"
+	xmlns:mdrpi="urn:oasis:names:tc:SAML:metadata:rpi"
+	xmlns:mdui="urn:oasis:names:tc:SAML:metadata:ui"
+	xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
+	xmlns:remd="http://refeds.org/metadata"
+	xmlns:icmd="http://id.incommon.org/metadata"
+	xmlns:date="http://exslt.org/dates-and-times">
+
+	<!-- search-and-replace constants -->
+	<xsl:variable name="double_quote" select="'&quot;'"/>
+	<xsl:variable name="double_double_quote" select="'&quot;&quot;'"/>
+	<xsl:variable name="timestamp" select="date:date-time()"/>
+	<!-- line feed -->
+	<xsl:variable name="lf" select="'&#10;'" />
+
+	<!-- output is plain text -->
+	<xsl:output method="text"/>
+
+	<!-- output the heading line -->
+	<xsl:template match="/" name="baseline_report">
+		<!-- Set field_sep to "','" (comma) for CSV (default),
+			or "'&#9;'" (tab) for Tab-delimited text -->
+		<xsl:param name="field_sep" select="','"/>
+
+		<xsl:text>#entityID</xsl:text>
+		<xsl:value-of select="$field_sep" disable-output-escaping="yes" />
+		<xsl:text>timestamp</xsl:text>
+		<xsl:value-of select="$field_sep" />
+		<xsl:text>Organization Display Name</xsl:text>
+		<xsl:value-of select="$field_sep" />
+		<xsl:text>Entity Type</xsl:text>
+		<xsl:value-of select="$field_sep" />
+		<xsl:text>Entity Display Name</xsl:text>
+		<xsl:value-of select="$field_sep" />
+		<xsl:text>Privacy URL</xsl:text>
+		<xsl:value-of select="$field_sep" />
+		<xsl:text>Logo Present</xsl:text>
+		<xsl:value-of select="$field_sep" />
+		<xsl:text>Tech Contact</xsl:text>
+		<xsl:value-of select="$field_sep" />
+		<xsl:text>Admin Contact</xsl:text>
+		<xsl:value-of select="$field_sep" />
+		<xsl:text>Security Contact</xsl:text>
+		<xsl:value-of select="$field_sep" />
+		<xsl:text>Complies</xsl:text>
+		<xsl:value-of select="$lf" />
+		<xsl:apply-templates>
+			<xsl:with-param name="field_sep" select="$field_sep" />
+		</xsl:apply-templates>
+	</xsl:template>
+
+	<!-- match all entity descriptors with an IdP role -->
+	<xsl:template match="//md:EntityDescriptor[md:Extensions/mdrpi:RegistrationInfo/@registrationAuthority='https://incommon.org'][md:IDPSSODescriptor]">
+
+		<xsl:param name="field_sep" select="','"/>
+
+		<!-- compute the normalized values of mdui:DisplayName and md:OrganizationDisplayName -->
+		<xsl:variable name="displayName" select="normalize-space(md:IDPSSODescriptor/md:Extensions/mdui:UIInfo/mdui:DisplayName[@xml:lang='en'])"/>
+		<xsl:variable name="orgDisplayName" select="normalize-space(md:Organization/md:OrganizationDisplayName[@xml:lang='en'])"/>
+		<xsl:variable name="privacyURL" select="normalize-space(md:IDPSSODescriptor/md:Extensions/mdui:UIInfo/mdui:PrivacyStatementURL[@xml:lang='en'])"/>
+		<xsl:variable name="logo" select="normalize-space(md:IDPSSODescriptor/md:Extensions/mdui:UIInfo/mdui:Logo)"/>
+		<xsl:variable name="techContact" select="normalize-space(md:ContactPerson[@contactType='technical']/md:EmailAddress)"/>
+		<xsl:variable name="adminContact" select="normalize-space(md:ContactPerson[@contactType='administrative']/md:EmailAddress)"/>
+		<xsl:variable name="rEFEDSSecurityContact" select="normalize-space(md:ContactPerson[@remd:contactType='http://refeds.org/metadata/contactType/security']/md:EmailAddress)"/>
+		<xsl:variable name="inCommonSecurityContact" select="normalize-space(md:ContactPerson[@icmd:contactType='http://id.incommon.org/metadata/contactType/security']/md:EmailAddress)"/>
+
+		<!-- output the IdP entityID -->
+		<xsl:value-of select="@entityID"/>
+
+		<!-- output the current timestamp -->
+		<xsl:value-of select="$field_sep" />
+		<xsl:value-of select="$timestamp"/>
+
+		<!-- output md:OrganizationDisplayName or "NONE" (in that order) -->
+		<xsl:value-of select="$field_sep" />
+		<xsl:choose>
+			<xsl:when test="$orgDisplayName != ''">
+				<!-- escape literal double quotes in md:OrganizationDisplayName -->
+				<xsl:variable name="escapedOrgDisplayName">
+					<xsl:call-template name="string-replace-all">
+						<xsl:with-param name="string" select="$orgDisplayName"/>
+						<xsl:with-param name="search" select="$double_quote"/>
+						<xsl:with-param name="replace" select="$double_double_quote"/>
+					</xsl:call-template>
+				</xsl:variable>
+				<xsl:text>"</xsl:text>
+				<xsl:value-of select="$escapedOrgDisplayName"/>
+				<xsl:text>"</xsl:text>
+			</xsl:when>
+			<xsl:otherwise>
+				<xsl:text>NONE</xsl:text>
+			</xsl:otherwise>
+		</xsl:choose>
+
+		<!-- output that this is an IdP entity descriptor -->
+		<xsl:value-of select="$field_sep" />
+		<xsl:text>IdP</xsl:text>
+
+		<!-- output either mdui:DisplayName or md:OrganizationDisplayName or "NONE" (in that order) -->
+		<xsl:value-of select="$field_sep" />
+		<xsl:choose>
+			<xsl:when test="$displayName != ''">
+				<!-- escape literal double quotes in mdui:DisplayName -->
+				<xsl:variable name="escapedDisplayName">
+					<xsl:call-template name="string-replace-all">
+						<xsl:with-param name="string" select="$displayName"/>
+						<xsl:with-param name="search" select="$double_quote"/>
+						<xsl:with-param name="replace" select="$double_double_quote"/>
+					</xsl:call-template>
+				</xsl:variable>
+				<xsl:text>"</xsl:text>
+				<xsl:value-of select="$escapedDisplayName"/>
+				<xsl:text>"</xsl:text>
+			</xsl:when>
+			<xsl:otherwise>
+				<xsl:text>NONE</xsl:text>
+			</xsl:otherwise>
+		</xsl:choose>
+
+		<!-- output either mdui:privacyURL or "NONE" (in that order) -->
+		<xsl:value-of select="$field_sep" />
+		<xsl:choose>
+			<xsl:when test="$privacyURL != ''">
+				<xsl:text>"</xsl:text>
+				<xsl:value-of select="$privacyURL"/>
+				<xsl:text>"</xsl:text>
+			</xsl:when>
+			<xsl:otherwise>
+				<xsl:text>NONE</xsl:text>
+			</xsl:otherwise>
+		</xsl:choose>
+
+		<!-- output Logo Present "TRUE" or "FALSE" -->
+		<xsl:value-of select="$field_sep" />
+		<xsl:choose>
+			<xsl:when test="$logo != ''">
+				<xsl:text>TRUE</xsl:text>
+			</xsl:when>
+			<xsl:otherwise>
+				<xsl:text>FALSE</xsl:text>
+			</xsl:otherwise>
+		</xsl:choose>
+
+		<!-- output either tech contact or "NONE" (in that order) -->
+		<xsl:value-of select="$field_sep" />
+		<xsl:choose>
+			<xsl:when test="$techContact != ''">
+				<xsl:text>"</xsl:text>
+				<xsl:value-of select="$techContact"/>
+				<xsl:text>"</xsl:text>
+			</xsl:when>
+			<xsl:otherwise>
+				<xsl:text>NONE</xsl:text>
+			</xsl:otherwise>
+		</xsl:choose>
+
+		<!-- output either admin contact or "NONE" (in that order) -->
+		<xsl:value-of select="$field_sep" />
+		<xsl:choose>
+			<xsl:when test="$adminContact != ''">
+				<xsl:text>"</xsl:text>
+				<xsl:value-of select="$adminContact"/>
+				<xsl:text>"</xsl:text>
+			</xsl:when>
+			<xsl:otherwise>
+				<xsl:text>NONE</xsl:text>
+			</xsl:otherwise>
+		</xsl:choose>
+
+		<!-- output either REFEDS security contact, InCommon security contact or "NONE" (in that order) -->
+		<xsl:value-of select="$field_sep" />
+		<xsl:choose>
+			<xsl:when test="$rEFEDSSecurityContact != ''">
+				<xsl:text>"</xsl:text>
+				<xsl:value-of select="$rEFEDSSecurityContact"/>
+				<xsl:text>"</xsl:text>
+			</xsl:when>
+			<xsl:when test="$inCommonSecurityContact != ''">
+				<xsl:text>"</xsl:text>
+				<xsl:value-of select="$inCommonSecurityContact"/>
+				<xsl:text>"</xsl:text>
+			</xsl:when>
+			<xsl:otherwise>
+				<xsl:text>NONE</xsl:text>
+			</xsl:otherwise>
+		</xsl:choose>
+
+		<!-- output "Y" (complies) or "N" (does not comply) with baseline expectations -->
+		<xsl:value-of select="$field_sep" />
+		<xsl:choose>
+			<xsl:when test="$privacyURL != '' and $logo != '' and $techContact != '' and $adminContact != '' and ($rEFEDSSecurityContact != '' or $inCommonSecurityContact != '')">
+				<xsl:text>Y</xsl:text>
+			</xsl:when>
+			<xsl:otherwise>
+				<xsl:text>N</xsl:text>
+			</xsl:otherwise>
+		</xsl:choose>
+
+		<!-- output "hide" or "show" depending on the hide-from-discovery entity attribute -->
+<!--
+		<xsl:value-of select="$field_sep" />
+		<xsl:choose>
+			<xsl:when test="md:Extensions/mdattr:EntityAttributes/saml:Attribute[@Name='http://macedir.org/entity-category']/saml:AttributeValue[text()='http://refeds.org/category/hide-from-discovery']">
+				<xsl:text>hide</xsl:text>
+			</xsl:when>
+			<xsl:otherwise>
+				<xsl:text>show</xsl:text>
+			</xsl:otherwise>
+		</xsl:choose>
+-->
+
+		<!-- output the registrar ID -->
+<!--
+		<xsl:value-of select="$field_sep" />
+		<xsl:value-of select="md:Extensions/mdrpi:RegistrationInfo/@registrationAuthority"/>
+-->
+
+		<xsl:text>&#x0a;</xsl:text>
+	</xsl:template>
+
+	<!-- match all entity descriptors with an SP role -->
+	<xsl:template match="//md:EntityDescriptor[md:Extensions/mdrpi:RegistrationInfo/@registrationAuthority='https://incommon.org'][md:SPSSODescriptor]">
+
+		<xsl:param name="field_sep" select="','"/>
+
+		<!-- compute the normalized values of mdui:DisplayName and md:OrganizationDisplayName -->
+		<xsl:variable name="displayName" select="normalize-space(md:SPSSODescriptor/md:Extensions/mdui:UIInfo/mdui:DisplayName[@xml:lang='en'])"/>
+		<xsl:variable name="orgDisplayName" select="normalize-space(md:Organization/md:OrganizationDisplayName[@xml:lang='en'])"/>
+		<xsl:variable name="privacyURL" select="normalize-space(md:SPSSODescriptor/md:Extensions/mdui:UIInfo/mdui:PrivacyStatementURL[@xml:lang='en'])"/>
+		<xsl:variable name="logo" select="normalize-space(md:SPSSODescriptor/md:Extensions/mdui:UIInfo/mdui:Logo)"/>
+		<xsl:variable name="techContact" select="normalize-space(md:ContactPerson[@contactType='technical']/md:EmailAddress)"/>
+		<xsl:variable name="adminContact" select="normalize-space(md:ContactPerson[@contactType='administrative']/md:EmailAddress)"/>
+		<xsl:variable name="rEFEDSSecurityContact" select="normalize-space(md:ContactPerson[@remd:contactType='http://refeds.org/metadata/contactType/security']/md:EmailAddress)"/>
+		<xsl:variable name="inCommonSecurityContact" select="normalize-space(md:ContactPerson[@icmd:contactType='http://id.incommon.org/metadata/contactType/security']/md:EmailAddress)"/>
+
+		<!-- output the SP entityID -->
+		<xsl:value-of select="@entityID"/>
+
+		<!-- output the current timestamp -->
+		<xsl:value-of select="$field_sep" />
+		<xsl:value-of select="$timestamp"/>
+
+		<!-- output md:OrganizationDisplayName or "NONE" (in that order) -->
+		<xsl:value-of select="$field_sep" />
+		<xsl:choose>
+			<xsl:when test="$orgDisplayName != ''">
+				<!-- escape literal double quotes in md:OrganizationDisplayName -->
+				<xsl:variable name="escapedOrgDisplayName">
+					<xsl:call-template name="string-replace-all">
+						<xsl:with-param name="string" select="$orgDisplayName"/>
+						<xsl:with-param name="search" select="$double_quote"/>
+						<xsl:with-param name="replace" select="$double_double_quote"/>
+					</xsl:call-template>
+				</xsl:variable>
+				<xsl:text>"</xsl:text>
+				<xsl:value-of select="$escapedOrgDisplayName"/>
+				<xsl:text>"</xsl:text>
+			</xsl:when>
+			<xsl:otherwise>
+				<xsl:text>NONE</xsl:text>
+			</xsl:otherwise>
+		</xsl:choose>
+
+
+		<!-- output that this is an SP entity descriptor -->
+		<xsl:value-of select="$field_sep" />
+		<xsl:text>SP</xsl:text>
+
+		<!-- output either mdui:DisplayName or md:OrganizationDisplayName or "NONE" (in that order) -->
+		<xsl:value-of select="$field_sep" />
+		<xsl:choose>
+			<xsl:when test="$displayName != ''">
+				<!-- escape literal double quotes in mdui:DisplayName -->
+				<xsl:variable name="escapedDisplayName">
+					<xsl:call-template name="string-replace-all">
+						<xsl:with-param name="string" select="$displayName"/>
+						<xsl:with-param name="search" select="$double_quote"/>
+						<xsl:with-param name="replace" select="$double_double_quote"/>
+					</xsl:call-template>
+				</xsl:variable>
+				<xsl:text>"</xsl:text>
+				<xsl:value-of select="$escapedDisplayName"/>
+				<xsl:text>"</xsl:text>
+			</xsl:when>
+			<xsl:otherwise>
+				<xsl:text>NONE</xsl:text>
+			</xsl:otherwise>
+		</xsl:choose>
+
+		<!-- output either mdui:privacyURL or "NONE" (in that order) -->
+		<xsl:value-of select="$field_sep" />
+		<xsl:choose>
+			<xsl:when test="$privacyURL != ''">
+				<xsl:text>"</xsl:text>
+				<xsl:value-of select="$privacyURL"/>
+				<xsl:text>"</xsl:text>
+			</xsl:when>
+			<xsl:otherwise>
+				<xsl:text>NONE</xsl:text>
+			</xsl:otherwise>
+		</xsl:choose>
+
+		<!-- output Logo Present "TRUE" or "FALSE" -->
+		<xsl:value-of select="$field_sep" />
+		<xsl:choose>
+			<xsl:when test="$logo != ''">
+				<xsl:text>TRUE</xsl:text>
+			</xsl:when>
+			<xsl:otherwise>
+				<xsl:text>FALSE</xsl:text>
+			</xsl:otherwise>
+		</xsl:choose>
+
+		<!-- output either tech contact or "NONE" (in that order) -->
+		<xsl:value-of select="$field_sep" />
+		<xsl:choose>
+			<xsl:when test="$techContact != ''">
+				<xsl:text>"</xsl:text>
+				<xsl:value-of select="$techContact"/>
+				<xsl:text>"</xsl:text>
+			</xsl:when>
+			<xsl:otherwise>
+				<xsl:text>NONE</xsl:text>
+			</xsl:otherwise>
+		</xsl:choose>
+
+		<!-- output either admin contact or "NONE" (in that order) -->
+		<xsl:value-of select="$field_sep" />
+		<xsl:choose>
+			<xsl:when test="$adminContact != ''">
+				<xsl:text>"</xsl:text>
+				<xsl:value-of select="$adminContact"/>
+				<xsl:text>"</xsl:text>
+			</xsl:when>
+			<xsl:otherwise>
+				<xsl:text>NONE</xsl:text>
+			</xsl:otherwise>
+		</xsl:choose>
+
+		<!-- output either REFEDS security contact, InCommon security contact or "NONE" (in that order) -->
+		<xsl:value-of select="$field_sep" />
+		<xsl:choose>
+			<xsl:when test="$rEFEDSSecurityContact != ''">
+				<xsl:text>"</xsl:text>
+				<xsl:value-of select="$rEFEDSSecurityContact"/>
+				<xsl:text>"</xsl:text>
+			</xsl:when>
+			<xsl:when test="$inCommonSecurityContact != ''">
+				<xsl:text>"</xsl:text>
+				<xsl:value-of select="$inCommonSecurityContact"/>
+				<xsl:text>"</xsl:text>
+			</xsl:when>
+			<xsl:otherwise>
+				<xsl:text>NONE</xsl:text>
+			</xsl:otherwise>
+		</xsl:choose>
+
+		<!-- output "Y" (complies) or "N" (does not comply) with baseline expectations -->
+		<xsl:value-of select="$field_sep" />
+		<xsl:choose>
+			<xsl:when test="$privacyURL != '' and $logo != '' and $techContact != '' and $adminContact != '' and ($rEFEDSSecurityContact != '' or $inCommonSecurityContact != '')">
+				<xsl:text>Y</xsl:text>
+			</xsl:when>
+			<xsl:otherwise>
+				<xsl:text>N</xsl:text>
+			</xsl:otherwise>
+		</xsl:choose>
+
+		<!-- output the registrar ID -->
+<!--
+		<xsl:value-of select="$field_sep" />
+		<xsl:value-of select="md:Extensions/mdrpi:RegistrationInfo/@registrationAuthority"/>
+-->
+
+		<xsl:text>&#x0a;</xsl:text>
+	</xsl:template>
+
+	<!--
+		A named template that performs global (recursive) search-and-replace on a string
+		(similar to fn:replace(string, pattern, replace) in XSLT 2.0).
+		See: http://stackoverflow.com/questions/3067113/xslt-string-replace/3067130#3067130
+	-->
+	<xsl:template name="string-replace-all">
+		<xsl:param name="string"/>
+		<xsl:param name="search"/>
+		<xsl:param name="replace"/>
+		<xsl:choose>
+			<xsl:when test="$string = '' or $search = '' or not($search)">
+				<!-- Prevent this routine from hanging -->
+				<xsl:value-of select="$string"/>
+			</xsl:when>
+			<xsl:when test="contains($string, $search)">
+				<xsl:value-of select="substring-before($string, $search)"/>
+				<xsl:value-of select="$replace"/>
+				<xsl:call-template name="string-replace-all">
+					<xsl:with-param name="string" select="substring-after($string, $search)"/>
+					<xsl:with-param name="search" select="$search"/>
+					<xsl:with-param name="replace" select="$replace"/>
+				</xsl:call-template>
+			</xsl:when>
+			<xsl:otherwise>
+				<xsl:value-of select="$string"/>
+			</xsl:otherwise>
+		</xsl:choose>
+	</xsl:template>
+
+	<xsl:template match="text()">
+		<!-- do nothing -->
+	</xsl:template>
+</xsl:stylesheet>
diff --git a/lib/baseline_report_csv.xsl b/lib/baseline_report_csv.xsl
index 8005510..87c4de8 100644
--- a/lib/baseline_report_csv.xsl
+++ b/lib/baseline_report_csv.xsl
@@ -13,430 +13,16 @@
 	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 	See the License for the specific language governing permissions and
 	limitations under the License.
--->
-<!--
-	baseline_report_csv.xsl
-	
-	This XSL transform takes a SAML metadata aggregate and produces a list of
-	IdP and SP baseline compliance factors (documented at [1]) for entities registered by 'https://incommon.org', containing the following fields:
-
-IdPs:
-      1. entityID
-      2. timestamp
-      3. Organization Display Name
-      4. "IdP" string literal
-	  5. IdP Display Name: mdui:DisplayName
-	  6. IdP Privacy URL or NONE
-	  7. IdP Logo present TRUE/FALSE
-	  8. IdP Tech contact or NONE
-	  9. IdP Admin contact or NONE
-	  10. IdP Security contact or NONE
-	  11. IdP complies with baseline expectations "Y" or "N"
-	
-	The IdP Display Name field is set to "NONE" if neither mdui:DisplayName nor 
-	md:OrganizationDisplayName exist in the entity descriptor. 
-	
-	BUG: Incorrectly processes an entity with multiple IDPSSODescriptor elements
-
-SPs:
-      1. entityID
-      2. timestamp
-      3. Organization Display Name
-      4. "SP" string literal
-	  5. SP Display Name: mdui:DisplayName
-	  6. SP Privacy URL or NONE
-	  7. SP Logo present TRUE/FALSE
-	  8. SP Tech contact or NONE
-	  9. SP Admin contact or NONE
-	  10. SP Security contact or NONE
-	  11. SP complies with baseline expectations "Y" or "N"
-
-	The SP Display Name field is set to "NONE" if neither mdui:DisplayName nor
-	md:OrganizationDisplayName exist in the entity descriptor.
-
-	BUG: Incorrectly processes an entity with multiple SPSSODescriptor elements
-
-[1] https://spaces.internet2.edu/x/4RL9Bg
-
 -->
 <xsl:stylesheet version="1.0"
-	xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
-	xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata"
-	xmlns:mdattr="urn:oasis:names:tc:SAML:metadata:attribute"
-	xmlns:mdrpi="urn:oasis:names:tc:SAML:metadata:rpi"
-	xmlns:mdui="urn:oasis:names:tc:SAML:metadata:ui"
-	xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
-	xmlns:remd="http://refeds.org/metadata"
-	xmlns:icmd="http://id.incommon.org/metadata"
-	xmlns:date="http://exslt.org/dates-and-times">
-	
-	<!-- search-and-replace constants -->
-	<xsl:variable name="double_quote" select="'&quot;'"/>
-	<xsl:variable name="double_double_quote" select="'&quot;&quot;'"/>
-	<xsl:variable name="timestamp" select="date:date-time()"/>
+	xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 
-	<!-- output is plain text -->
-	<xsl:output method="text"/>
+	<xsl:include href="baseline_report.xsl" />
 
-	<!-- output the heading line -->
 	<xsl:template match="/">
-		<xsl:text>entityID,timestamp,Organization Display Name,Entity Type,Entity Display Name,Privacy URL,Logo Present,Tech Contact,Admin Contact,Security Contact,Complies</xsl:text>
-		<xsl:text>&#x0a;</xsl:text>
-		<xsl:apply-templates/>
-	</xsl:template>
-
-	<!-- match all entity descriptors with an IdP role -->
-	<xsl:template match="//md:EntityDescriptor[md:Extensions/mdrpi:RegistrationInfo/@registrationAuthority='https://incommon.org'][md:IDPSSODescriptor]">
-	
-		<!-- compute the normalized values of mdui:DisplayName and md:OrganizationDisplayName -->
-		<xsl:variable name="displayName" select="normalize-space(md:IDPSSODescriptor/md:Extensions/mdui:UIInfo/mdui:DisplayName[@xml:lang='en'])"/>
-		<xsl:variable name="orgDisplayName" select="normalize-space(md:Organization/md:OrganizationDisplayName[@xml:lang='en'])"/>
-		<xsl:variable name="privacyURL" select="normalize-space(md:IDPSSODescriptor/md:Extensions/mdui:UIInfo/mdui:PrivacyStatementURL[@xml:lang='en'])"/>
-		<xsl:variable name="logo" select="normalize-space(md:IDPSSODescriptor/md:Extensions/mdui:UIInfo/mdui:Logo)"/>
-		<xsl:variable name="techContact" select="normalize-space(md:ContactPerson[@contactType='technical']/md:EmailAddress)"/>
-		<xsl:variable name="adminContact" select="normalize-space(md:ContactPerson[@contactType='administrative']/md:EmailAddress)"/>
-		<xsl:variable name="rEFEDSSecurityContact" select="normalize-space(md:ContactPerson[@remd:contactType='http://refeds.org/metadata/contactType/security']/md:EmailAddress)"/>
-		<xsl:variable name="inCommonSecurityContact" select="normalize-space(md:ContactPerson[@icmd:contactType='http://id.incommon.org/metadata/contactType/security']/md:EmailAddress)"/>	
-
-		<!-- output the IdP entityID -->
-		<xsl:value-of select="@entityID"/>
-		
-		<!-- output the current timestamp -->
-		<xsl:text>,</xsl:text>
-		<xsl:value-of select="$timestamp"/>
-
-		<!-- output md:OrganizationDisplayName or "NONE" (in that order) -->
-		<xsl:text>,</xsl:text>
-		<xsl:choose>
-			<xsl:when test="$orgDisplayName != ''">
-				<!-- escape literal double quotes in md:OrganizationDisplayName -->
-				<xsl:variable name="escapedOrgDisplayName">
-					<xsl:call-template name="string-replace-all">
-						<xsl:with-param name="string" select="$orgDisplayName"/>
-						<xsl:with-param name="search" select="$double_quote"/>
-						<xsl:with-param name="replace" select="$double_double_quote"/>
-					</xsl:call-template>
-				</xsl:variable>
-				<xsl:text>"</xsl:text>
-				<xsl:value-of select="$escapedOrgDisplayName"/>
-				<xsl:text>"</xsl:text>
-			</xsl:when>
-			<xsl:otherwise>
-				<xsl:text>NONE</xsl:text>
-			</xsl:otherwise>
-		</xsl:choose>
-	
-		<!-- output that this is an IdP entity descriptor -->
-		<xsl:text>,</xsl:text>
-		<xsl:text>IdP</xsl:text>
-	
-		<!-- output either mdui:DisplayName or md:OrganizationDisplayName or "NONE" (in that order) -->
-		<xsl:text>,</xsl:text>
-		<xsl:choose>
-			<xsl:when test="$displayName != ''">
-				<!-- escape literal double quotes in mdui:DisplayName -->
-				<xsl:variable name="escapedDisplayName">
-					<xsl:call-template name="string-replace-all">
-						<xsl:with-param name="string" select="$displayName"/>
-						<xsl:with-param name="search" select="$double_quote"/>
-						<xsl:with-param name="replace" select="$double_double_quote"/>
-					</xsl:call-template>
-				</xsl:variable>
-				<xsl:text>"</xsl:text>
-				<xsl:value-of select="$escapedDisplayName"/>
-				<xsl:text>"</xsl:text>
-			</xsl:when>
-			<xsl:otherwise>
-				<xsl:text>NONE</xsl:text>
-			</xsl:otherwise>
-		</xsl:choose>
-		
-		<!-- output either mdui:privacyURL or "NONE" (in that order) -->
-		<xsl:text>,</xsl:text>
-		<xsl:choose>
-			<xsl:when test="$privacyURL != ''">
-				<xsl:text>"</xsl:text>
-				<xsl:value-of select="$privacyURL"/>
-				<xsl:text>"</xsl:text>
-			</xsl:when>
-			<xsl:otherwise>
-				<xsl:text>NONE</xsl:text>
-			</xsl:otherwise>
-		</xsl:choose>
-		
-		<!-- output Logo Present "TRUE" or "FALSE" -->
-		<xsl:text>,</xsl:text>
-		<xsl:choose>
-			<xsl:when test="$logo != ''">
-				<xsl:text>TRUE</xsl:text>
-			</xsl:when>
-			<xsl:otherwise>
-				<xsl:text>FALSE</xsl:text>
-			</xsl:otherwise>
-		</xsl:choose>	
-		
-		<!-- output either tech contact or "NONE" (in that order) -->
-		<xsl:text>,</xsl:text>
-		<xsl:choose>
-			<xsl:when test="$techContact != ''">
-				<xsl:text>"</xsl:text>
-				<xsl:value-of select="$techContact"/>
-				<xsl:text>"</xsl:text>
-			</xsl:when>
-			<xsl:otherwise>
-				<xsl:text>NONE</xsl:text>
-			</xsl:otherwise>
-		</xsl:choose>
-		
-		<!-- output either admin contact or "NONE" (in that order) -->
-		<xsl:text>,</xsl:text>
-		<xsl:choose>
-			<xsl:when test="$adminContact != ''">
-				<xsl:text>"</xsl:text>
-				<xsl:value-of select="$adminContact"/>
-				<xsl:text>"</xsl:text>
-			</xsl:when>
-			<xsl:otherwise>
-				<xsl:text>NONE</xsl:text>
-			</xsl:otherwise>
-		</xsl:choose>
-		
-		<!-- output either REFEDS security contact, InCommon security contact or "NONE" (in that order) -->
-		<xsl:text>,</xsl:text>
-		<xsl:choose>
-			<xsl:when test="$rEFEDSSecurityContact != ''">
-				<xsl:text>"</xsl:text>
-				<xsl:value-of select="$rEFEDSSecurityContact"/>
-				<xsl:text>"</xsl:text>
-			</xsl:when>
-			<xsl:when test="$inCommonSecurityContact != ''">
-				<xsl:text>"</xsl:text>
-				<xsl:value-of select="$inCommonSecurityContact"/>
-				<xsl:text>"</xsl:text>
-			</xsl:when>
-			<xsl:otherwise>
-				<xsl:text>NONE</xsl:text>
-			</xsl:otherwise>
-		</xsl:choose>
-		
-		<!-- output "Y" (complies) or "N" (does not comply) with baseline expectations -->
-		<xsl:text>,</xsl:text>
-		<xsl:choose>
-			<xsl:when test="$privacyURL != '' and $logo != '' and $techContact != '' and $adminContact != '' and ($rEFEDSSecurityContact != '' or $inCommonSecurityContact != '')">
-				<xsl:text>Y</xsl:text>
-			</xsl:when>
-			<xsl:otherwise>
-				<xsl:text>N</xsl:text>
-			</xsl:otherwise>
-		</xsl:choose>
-		
-		<!-- output "hide" or "show" depending on the hide-from-discovery entity attribute -->
-<!--
-		<xsl:text>,</xsl:text>
-		<xsl:choose>
-			<xsl:when test="md:Extensions/mdattr:EntityAttributes/saml:Attribute[@Name='http://macedir.org/entity-category']/saml:AttributeValue[text()='http://refeds.org/category/hide-from-discovery']">
-				<xsl:text>hide</xsl:text>
-			</xsl:when>
-			<xsl:otherwise>
-				<xsl:text>show</xsl:text>
-			</xsl:otherwise>
-		</xsl:choose>
--->
-
-		<!-- output the registrar ID -->
-<!--
-		<xsl:text>,</xsl:text>
-		<xsl:value-of select="md:Extensions/mdrpi:RegistrationInfo/@registrationAuthority"/>
--->
-
-		<xsl:text>&#x0a;</xsl:text>
+		<xsl:call-template name="baseline_report">
+			<xsl:with-param name="field_sep" select="','" />
+		</xsl:call-template>
 	</xsl:template>
-	
-	<!-- match all entity descriptors with an SP role -->
-	<xsl:template match="//md:EntityDescriptor[md:Extensions/mdrpi:RegistrationInfo/@registrationAuthority='https://incommon.org'][md:SPSSODescriptor]">
 
-		<!-- compute the normalized values of mdui:DisplayName and md:OrganizationDisplayName -->
-		<xsl:variable name="displayName" select="normalize-space(md:SPSSODescriptor/md:Extensions/mdui:UIInfo/mdui:DisplayName[@xml:lang='en'])"/>
-		<xsl:variable name="orgDisplayName" select="normalize-space(md:Organization/md:OrganizationDisplayName[@xml:lang='en'])"/>
-		<xsl:variable name="privacyURL" select="normalize-space(md:SPSSODescriptor/md:Extensions/mdui:UIInfo/mdui:PrivacyStatementURL[@xml:lang='en'])"/>
-		<xsl:variable name="logo" select="normalize-space(md:SPSSODescriptor/md:Extensions/mdui:UIInfo/mdui:Logo)"/>
-		<xsl:variable name="techContact" select="normalize-space(md:ContactPerson[@contactType='technical']/md:EmailAddress)"/>
-		<xsl:variable name="adminContact" select="normalize-space(md:ContactPerson[@contactType='administrative']/md:EmailAddress)"/>
-		<xsl:variable name="rEFEDSSecurityContact" select="normalize-space(md:ContactPerson[@remd:contactType='http://refeds.org/metadata/contactType/security']/md:EmailAddress)"/>
-		<xsl:variable name="inCommonSecurityContact" select="normalize-space(md:ContactPerson[@icmd:contactType='http://id.incommon.org/metadata/contactType/security']/md:EmailAddress)"/>
-
-		<!-- output the SP entityID -->
-		<xsl:value-of select="@entityID"/>
-		
-		<!-- output the current timestamp -->
-		<xsl:text>,</xsl:text>
-		<xsl:value-of select="$timestamp"/>
-
-		<!-- output md:OrganizationDisplayName or "NONE" (in that order) -->
-		<xsl:text>,</xsl:text>
-		<xsl:choose>
-			<xsl:when test="$orgDisplayName != ''">
-				<!-- escape literal double quotes in md:OrganizationDisplayName -->
-				<xsl:variable name="escapedOrgDisplayName">
-					<xsl:call-template name="string-replace-all">
-						<xsl:with-param name="string" select="$orgDisplayName"/>
-						<xsl:with-param name="search" select="$double_quote"/>
-						<xsl:with-param name="replace" select="$double_double_quote"/>
-					</xsl:call-template>
-				</xsl:variable>
-				<xsl:text>"</xsl:text>
-				<xsl:value-of select="$escapedOrgDisplayName"/>
-				<xsl:text>"</xsl:text>
-			</xsl:when>
-			<xsl:otherwise>
-				<xsl:text>NONE</xsl:text>
-			</xsl:otherwise>
-		</xsl:choose>
-
-		
-		<!-- output that this is an SP entity descriptor -->
-		<xsl:text>,</xsl:text>
-		<xsl:text>SP</xsl:text>
-
-		<!-- output either mdui:DisplayName or md:OrganizationDisplayName or "NONE" (in that order) -->
-		<xsl:text>,</xsl:text>
-		<xsl:choose>
-			<xsl:when test="$displayName != ''">
-				<!-- escape literal double quotes in mdui:DisplayName -->
-				<xsl:variable name="escapedDisplayName">
-					<xsl:call-template name="string-replace-all">
-						<xsl:with-param name="string" select="$displayName"/>
-						<xsl:with-param name="search" select="$double_quote"/>
-						<xsl:with-param name="replace" select="$double_double_quote"/>
-					</xsl:call-template>
-				</xsl:variable>
-				<xsl:text>"</xsl:text>
-				<xsl:value-of select="$escapedDisplayName"/>
-				<xsl:text>"</xsl:text>
-			</xsl:when>
-			<xsl:otherwise>
-				<xsl:text>NONE</xsl:text>
-			</xsl:otherwise>
-		</xsl:choose>
-
-		<!-- output either mdui:privacyURL or "NONE" (in that order) -->
-		<xsl:text>,</xsl:text>
-		<xsl:choose>
-			<xsl:when test="$privacyURL != ''">
-				<xsl:text>"</xsl:text>
-				<xsl:value-of select="$privacyURL"/>
-				<xsl:text>"</xsl:text>
-			</xsl:when>
-			<xsl:otherwise>
-				<xsl:text>NONE</xsl:text>
-			</xsl:otherwise>
-		</xsl:choose>
-
-		<!-- output Logo Present "TRUE" or "FALSE" -->
-		<xsl:text>,</xsl:text>
-		<xsl:choose>
-			<xsl:when test="$logo != ''">
-				<xsl:text>TRUE</xsl:text>
-			</xsl:when>
-			<xsl:otherwise>
-				<xsl:text>FALSE</xsl:text>
-			</xsl:otherwise>
-		</xsl:choose>
-
-		<!-- output either tech contact or "NONE" (in that order) -->
-		<xsl:text>,</xsl:text>
-		<xsl:choose>
-			<xsl:when test="$techContact != ''">
-				<xsl:text>"</xsl:text>
-				<xsl:value-of select="$techContact"/>
-				<xsl:text>"</xsl:text>
-			</xsl:when>
-			<xsl:otherwise>
-				<xsl:text>NONE</xsl:text>
-			</xsl:otherwise>
-		</xsl:choose>
-
-		<!-- output either admin contact or "NONE" (in that order) -->
-		<xsl:text>,</xsl:text>
-		<xsl:choose>
-			<xsl:when test="$adminContact != ''">
-				<xsl:text>"</xsl:text>
-				<xsl:value-of select="$adminContact"/>
-				<xsl:text>"</xsl:text>
-			</xsl:when>
-			<xsl:otherwise>
-				<xsl:text>NONE</xsl:text>
-			</xsl:otherwise>
-		</xsl:choose>
-
-		<!-- output either REFEDS security contact, InCommon security contact or "NONE" (in that order) -->
-		<xsl:text>,</xsl:text>
-		<xsl:choose>
-			<xsl:when test="$rEFEDSSecurityContact != ''">
-				<xsl:text>"</xsl:text>
-				<xsl:value-of select="$rEFEDSSecurityContact"/>
-				<xsl:text>"</xsl:text>
-			</xsl:when>
-			<xsl:when test="$inCommonSecurityContact != ''">
-				<xsl:text>"</xsl:text>
-				<xsl:value-of select="$inCommonSecurityContact"/>
-				<xsl:text>"</xsl:text>
-			</xsl:when>
-			<xsl:otherwise>
-				<xsl:text>NONE</xsl:text>
-			</xsl:otherwise>
-		</xsl:choose>
-		
-		<!-- output "Y" (complies) or "N" (does not comply) with baseline expectations -->
-		<xsl:text>,</xsl:text>
-		<xsl:choose>
-			<xsl:when test="$privacyURL != '' and $logo != '' and $techContact != '' and $adminContact != '' and ($rEFEDSSecurityContact != '' or $inCommonSecurityContact != '')">
-				<xsl:text>Y</xsl:text>
-			</xsl:when>
-			<xsl:otherwise>
-				<xsl:text>N</xsl:text>
-			</xsl:otherwise>
-		</xsl:choose>
-
-		<!-- output the registrar ID -->
-<!--
-		<xsl:text>,</xsl:text>
-		<xsl:value-of select="md:Extensions/mdrpi:RegistrationInfo/@registrationAuthority"/>
--->
-
-		<xsl:text>&#x0a;</xsl:text>
-	</xsl:template>
-
-	<!--
-		A named template that performs global (recursive) search-and-replace on a string
-		(similar to fn:replace(string, pattern, replace) in XSLT 2.0).
-		See: http://stackoverflow.com/questions/3067113/xslt-string-replace/3067130#3067130
-	-->
-	<xsl:template name="string-replace-all">
-		<xsl:param name="string"/>
-		<xsl:param name="search"/>
-		<xsl:param name="replace"/>
-		<xsl:choose>
-			<xsl:when test="$string = '' or $search = '' or not($search)">
-				<!-- Prevent this routine from hanging -->
-				<xsl:value-of select="$string"/>
-			</xsl:when>
-			<xsl:when test="contains($string, $search)">
-				<xsl:value-of select="substring-before($string, $search)"/>
-				<xsl:value-of select="$replace"/>
-				<xsl:call-template name="string-replace-all">
-					<xsl:with-param name="string" select="substring-after($string, $search)"/>
-					<xsl:with-param name="search" select="$search"/>
-					<xsl:with-param name="replace" select="$replace"/>
-				</xsl:call-template>
-			</xsl:when>
-			<xsl:otherwise>
-				<xsl:value-of select="$string"/>
-			</xsl:otherwise>
-		</xsl:choose>
-	</xsl:template>
-
-	<xsl:template match="text()">
-		<!-- do nothing -->
-	</xsl:template>
 </xsl:stylesheet>
diff --git a/lib/baseline_report_tsv.xsl b/lib/baseline_report_tsv.xsl
new file mode 100644
index 0000000..09fcf60
--- /dev/null
+++ b/lib/baseline_report_tsv.xsl
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+	Copyright 2018 Internet2
+
+	Licensed under the Apache License, Version 2.0 (the "License");
+	you may not use this file except in compliance with the License.
+	You may obtain a copy of the License at
+
+		http://www.apache.org/licenses/LICENSE-2.0
+
+	Unless required by applicable law or agreed to in writing, software
+	distributed under the License is distributed on an "AS IS" BASIS,
+	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+	See the License for the specific language governing permissions and
+	limitations under the License.
+-->
+<xsl:stylesheet version="1.0"
+	xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+
+	<xsl:include href="baseline_report.xsl" />
+
+	<xsl:template match="/">
+		<xsl:call-template name="baseline_report">
+			<xsl:with-param name="field_sep" select="'&#9;'" />
+		</xsl:call-template>
+	</xsl:template>
+
+</xsl:stylesheet>
diff --git a/lib/baseline_report_web.xsl b/lib/baseline_report_web.xsl
new file mode 100644
index 0000000..0411861
--- /dev/null
+++ b/lib/baseline_report_web.xsl
@@ -0,0 +1,409 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+	Copyright 2018 Internet2
+
+	Licensed under the Apache License, Version 2.0 (the "License");
+	you may not use this file except in compliance with the License.
+	You may obtain a copy of the License at
+
+		http://www.apache.org/licenses/LICENSE-2.0
+
+	Unless required by applicable law or agreed to in writing, software
+	distributed under the License is distributed on an "AS IS" BASIS,
+	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+	See the License for the specific language governing permissions and
+	limitations under the License.
+-->
+<!--
+	baseline_report_csv.xsl
+
+	This XSL transform takes a SAML metadata aggregate and produces a list of
+	IdP and SP baseline compliance factors (documented at [1]) for entities registered by 'https://incommon.org', containing the following fields:
+
+IdPs:
+      1. entityID
+      2. timestamp
+      3. Organization Display Name
+      4. "IdP" string literal
+	  5. IdP Display Name: mdui:DisplayName
+	  6. IdP Privacy URL or NONE
+	  7. IdP Logo present TRUE/FALSE
+	  8. IdP Tech contact or NONE
+	  9. IdP Admin contact or NONE
+	  10. IdP Security contact or NONE
+	  11. IdP complies with baseline expectations "Y" or "N"
+
+	The IdP Display Name field is set to "NONE" if neither mdui:DisplayName nor
+	md:OrganizationDisplayName exist in the entity descriptor.
+
+	BUG: Incorrectly processes an entity with multiple IDPSSODescriptor elements
+
+SPs:
+      1. entityID
+      2. timestamp
+      3. Organization Display Name
+      4. "SP" string literal
+	  5. SP Display Name: mdui:DisplayName
+	  6. SP Privacy URL or NONE
+	  7. SP Logo present TRUE/FALSE
+	  8. SP Tech contact or NONE
+	  9. SP Admin contact or NONE
+	  10. SP Security contact or NONE
+	  11. SP complies with baseline expectations "Y" or "N"
+
+	The SP Display Name field is set to "NONE" if neither mdui:DisplayName nor
+	md:OrganizationDisplayName exist in the entity descriptor.
+
+	BUG: Incorrectly processes an entity with multiple SPSSODescriptor elements
+
+[1] https://spaces.internet2.edu/x/4RL9Bg
+
+-->
+<xsl:stylesheet version="1.0"
+	xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+	xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata"
+	xmlns:mdattr="urn:oasis:names:tc:SAML:metadata:attribute"
+	xmlns:mdrpi="urn:oasis:names:tc:SAML:metadata:rpi"
+	xmlns:mdui="urn:oasis:names:tc:SAML:metadata:ui"
+	xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
+	xmlns:remd="http://refeds.org/metadata"
+	xmlns:icmd="http://id.incommon.org/metadata"
+	xmlns:date="http://exslt.org/dates-and-times">
+
+	<!-- search-and-replace constants -->
+	<xsl:variable name="double_quote" select="'&quot;'"/>
+	<xsl:variable name="double_double_quote" select="'&quot;&quot;'"/>
+	<xsl:variable name="timestamp" select="date:date-time()"/>
+	<!-- line feed -->
+	<xsl:variable name="lf" select="'&#10;'" />
+
+	<!-- output is plain text -->
+	<xsl:output method="text"/>
+
+	<!-- output the heading line -->
+	<xsl:template match="/" name="baseline_report">
+		<!-- Set field_sep to "','" (comma) for CSV (default),
+			or "'&#9;'" (tab) for Tab-delimited text -->
+		<xsl:param name="field_sep" select="','"/>
+
+		<xsl:text>Organization Display Name</xsl:text>
+		<xsl:value-of select="$field_sep" />
+		<xsl:text>Entity Display Name</xsl:text>
+		<xsl:value-of select="$field_sep" />
+		<xsl:text>Entity ID</xsl:text>
+		<xsl:value-of select="$field_sep" />
+		<xsl:text>Entity Type</xsl:text>
+		<xsl:value-of select="$field_sep" />
+		<xsl:text>Severity</xsl:text>
+		<xsl:value-of select="$field_sep" />
+		<xsl:text>Has Privacy Statement</xsl:text>
+		<xsl:value-of select="$field_sep" />
+		<xsl:text>Has Logo</xsl:text>
+		<xsl:value-of select="$field_sep" />
+		<xsl:text>Has All Contacts</xsl:text>
+		<xsl:value-of select="$lf" />
+		<xsl:apply-templates>
+			<xsl:with-param name="field_sep" select="$field_sep" />
+		</xsl:apply-templates>
+	</xsl:template>
+
+	<!-- match all entity descriptors with an IdP role -->
+	<xsl:template match="//md:EntityDescriptor[md:Extensions/mdrpi:RegistrationInfo/@registrationAuthority='https://incommon.org'][md:IDPSSODescriptor]">
+
+		<xsl:param name="field_sep" select="','"/>
+
+		<!-- compute the normalized values of mdui:DisplayName and md:OrganizationDisplayName -->
+		<xsl:variable name="displayName" select="normalize-space(md:IDPSSODescriptor/md:Extensions/mdui:UIInfo/mdui:DisplayName[@xml:lang='en'])"/>
+		<xsl:variable name="orgDisplayName" select="normalize-space(md:Organization/md:OrganizationDisplayName[@xml:lang='en'])"/>
+		<xsl:variable name="privacyURL" select="normalize-space(md:IDPSSODescriptor/md:Extensions/mdui:UIInfo/mdui:PrivacyStatementURL[@xml:lang='en'])"/>
+		<xsl:variable name="logo" select="normalize-space(md:IDPSSODescriptor/md:Extensions/mdui:UIInfo/mdui:Logo)"/>
+		<xsl:variable name="techContact" select="normalize-space(md:ContactPerson[@contactType='technical']/md:EmailAddress)"/>
+		<xsl:variable name="adminContact" select="normalize-space(md:ContactPerson[@contactType='administrative']/md:EmailAddress)"/>
+		<xsl:variable name="rEFEDSSecurityContact" select="normalize-space(md:ContactPerson[@remd:contactType='http://refeds.org/metadata/contactType/security']/md:EmailAddress)"/>
+		<xsl:variable name="inCommonSecurityContact" select="normalize-space(md:ContactPerson[@icmd:contactType='http://id.incommon.org/metadata/contactType/security']/md:EmailAddress)"/>
+		
+
+		<!-- Only output if BE not met -->
+		<xsl:choose>
+			<xsl:when test="$privacyURL = '' or $logo = '' or $techContact = '' or $adminContact = '' or ($rEFEDSSecurityContact = '' and $inCommonSecurityContact = '')">
+		
+		<xsl:choose>
+			<xsl:when test="$orgDisplayName != ''">
+				<!-- escape literal double quotes in md:OrganizationDisplayName -->
+				<xsl:variable name="escapedOrgDisplayName">
+					<xsl:call-template name="string-replace-all">
+						<xsl:with-param name="string" select="$orgDisplayName"/>
+						<xsl:with-param name="search" select="$double_quote"/>
+						<xsl:with-param name="replace" select="$double_double_quote"/>
+					</xsl:call-template>
+				</xsl:variable>
+				<xsl:text>"</xsl:text>
+				<xsl:value-of select="$escapedOrgDisplayName"/>
+				<xsl:text>"</xsl:text>
+			</xsl:when>
+			<xsl:otherwise>
+				<xsl:text>NONE</xsl:text>
+			</xsl:otherwise>
+		</xsl:choose>
+
+
+		<!-- output either mdui:DisplayName or md:OrganizationDisplayName or "NONE" (in that order) -->
+		<xsl:value-of select="$field_sep" />
+		<xsl:choose>
+			<xsl:when test="$displayName != ''">
+				<!-- escape literal double quotes in mdui:DisplayName -->
+				<xsl:variable name="escapedDisplayName">
+					<xsl:call-template name="string-replace-all">
+						<xsl:with-param name="string" select="$displayName"/>
+						<xsl:with-param name="search" select="$double_quote"/>
+						<xsl:with-param name="replace" select="$double_double_quote"/>
+					</xsl:call-template>
+				</xsl:variable>
+				<xsl:text>"</xsl:text>
+				<xsl:value-of select="$escapedDisplayName"/>
+				<xsl:text>"</xsl:text>
+			</xsl:when>
+			<xsl:otherwise>
+				<xsl:text>NONE</xsl:text>
+			</xsl:otherwise>
+		</xsl:choose>
+
+		<!-- output the IdP entityID -->
+		<xsl:value-of select="$field_sep" />
+		<xsl:value-of select="@entityID"/>
+
+
+		<!-- output that this is an IdP entity descriptor -->
+		<xsl:value-of select="$field_sep" />
+		<xsl:text>IdP</xsl:text>
+
+	
+		<!-- Severity Logic:
+		     	Serious = missing privacy statement and at least one contact
+			 	Moderate = missing either privacy statement or at least one contact
+				Minor = missing only the logo
+		-->
+		<xsl:value-of select="$field_sep" />
+		<xsl:choose>
+			<xsl:when test="$privacyURL = '' and ($techContact = '' or $adminContact = '' or ($rEFEDSSecurityContact = '' and $inCommonSecurityContact = ''))">
+				<xsl:text>Significant</xsl:text>
+			</xsl:when>
+			<xsl:when test="$privacyURL = '' or ($techContact = '' or $adminContact = '' or ($rEFEDSSecurityContact = '' and $inCommonSecurityContact = ''))">
+				<xsl:text>Moderate</xsl:text>
+			</xsl:when>
+			<xsl:when test="$logo = ''">
+				<xsl:text>Minor</xsl:text>
+			</xsl:when>
+			<xsl:otherwise>
+				<xsl:text>Unknown</xsl:text>
+			</xsl:otherwise>
+		</xsl:choose>
+		
+	
+		<!-- output either mdui:privacyURL (Yes) or (No) (in that order) -->
+		<xsl:value-of select="$field_sep" />
+		<xsl:choose>
+			<xsl:when test="$privacyURL != ''">
+				<xsl:text>YES</xsl:text>
+			</xsl:when>
+			<xsl:otherwise>
+				<xsl:text>NO</xsl:text>
+			</xsl:otherwise>
+		</xsl:choose>
+
+		<!-- output Logo Present "TRUE" or "FALSE" -->
+		<xsl:value-of select="$field_sep" />
+		<xsl:choose>
+			<xsl:when test="$logo != ''">
+				<xsl:text>YES</xsl:text>
+			</xsl:when>
+			<xsl:otherwise>
+				<xsl:text>NO</xsl:text>
+			</xsl:otherwise>
+		</xsl:choose>
+
+		<!-- output "Y" (complies) or "N" (does not comply) with whether all contacts are there or not -->
+		<xsl:value-of select="$field_sep" />
+		<xsl:choose>
+			<xsl:when test="$techContact != '' and $adminContact != '' and ($rEFEDSSecurityContact != '' or $inCommonSecurityContact != '')">
+				<xsl:text>YES</xsl:text>
+			</xsl:when>
+			<xsl:otherwise>
+				<xsl:text>NO</xsl:text>
+			</xsl:otherwise>
+		</xsl:choose>
+
+		<xsl:text>&#x0a;</xsl:text>
+
+	</xsl:when>
+	<!-- <xsl:otherwise>-IdP-</xsl:otherwise> -->
+	</xsl:choose>
+
+	</xsl:template>
+
+	<!-- match all entity descriptors with an SP role -->
+	<xsl:template match="//md:EntityDescriptor[md:Extensions/mdrpi:RegistrationInfo/@registrationAuthority='https://incommon.org'][md:SPSSODescriptor]">
+
+		<xsl:param name="field_sep" select="','"/>
+
+		<!-- compute the normalized values of mdui:DisplayName and md:OrganizationDisplayName -->
+		<xsl:variable name="displayName" select="normalize-space(md:SPSSODescriptor/md:Extensions/mdui:UIInfo/mdui:DisplayName[@xml:lang='en'])"/>
+		<xsl:variable name="orgDisplayName" select="normalize-space(md:Organization/md:OrganizationDisplayName[@xml:lang='en'])"/>
+		<xsl:variable name="privacyURL" select="normalize-space(md:SPSSODescriptor/md:Extensions/mdui:UIInfo/mdui:PrivacyStatementURL[@xml:lang='en'])"/>
+		<xsl:variable name="logo" select="normalize-space(md:SPSSODescriptor/md:Extensions/mdui:UIInfo/mdui:Logo)"/>
+		<xsl:variable name="techContact" select="normalize-space(md:ContactPerson[@contactType='technical']/md:EmailAddress)"/>
+		<xsl:variable name="adminContact" select="normalize-space(md:ContactPerson[@contactType='administrative']/md:EmailAddress)"/>
+		<xsl:variable name="rEFEDSSecurityContact" select="normalize-space(md:ContactPerson[@remd:contactType='http://refeds.org/metadata/contactType/security']/md:EmailAddress)"/>
+		<xsl:variable name="inCommonSecurityContact" select="normalize-space(md:ContactPerson[@icmd:contactType='http://id.incommon.org/metadata/contactType/security']/md:EmailAddress)"/>
+
+		<!-- Only output if BE not met -->
+		<xsl:choose>
+			<xsl:when test="$privacyURL = '' or $logo = '' or $techContact = '' or $adminContact = '' or ($rEFEDSSecurityContact = '' and $inCommonSecurityContact = '')">
+		
+		<xsl:choose>
+			<xsl:when test="$orgDisplayName != ''">
+				<!-- escape literal double quotes in md:OrganizationDisplayName -->
+				<xsl:variable name="escapedOrgDisplayName">
+					<xsl:call-template name="string-replace-all">
+						<xsl:with-param name="string" select="$orgDisplayName"/>
+						<xsl:with-param name="search" select="$double_quote"/>
+						<xsl:with-param name="replace" select="$double_double_quote"/>
+					</xsl:call-template>
+				</xsl:variable>
+				<xsl:text>"</xsl:text>
+				<xsl:value-of select="$escapedOrgDisplayName"/>
+				<xsl:text>"</xsl:text>
+			</xsl:when>
+			<xsl:otherwise>
+				<xsl:text>NONE</xsl:text>
+			</xsl:otherwise>
+		</xsl:choose>
+
+
+		<!-- output either mdui:DisplayName or md:OrganizationDisplayName or "NONE" (in that order) -->
+		<xsl:value-of select="$field_sep" />
+		<xsl:choose>
+			<xsl:when test="$displayName != ''">
+				<!-- escape literal double quotes in mdui:DisplayName -->
+				<xsl:variable name="escapedDisplayName">
+					<xsl:call-template name="string-replace-all">
+						<xsl:with-param name="string" select="$displayName"/>
+						<xsl:with-param name="search" select="$double_quote"/>
+						<xsl:with-param name="replace" select="$double_double_quote"/>
+					</xsl:call-template>
+				</xsl:variable>
+				<xsl:text>"</xsl:text>
+				<xsl:value-of select="$escapedDisplayName"/>
+				<xsl:text>"</xsl:text>
+			</xsl:when>
+			<xsl:otherwise>
+				<xsl:text>NONE</xsl:text>
+			</xsl:otherwise>
+		</xsl:choose>
+
+		<!-- output the IdP entityID -->
+		<xsl:value-of select="$field_sep" />
+		<xsl:value-of select="@entityID"/>
+
+
+		<!-- output that this is an IdP entity descriptor -->
+		<xsl:value-of select="$field_sep" />
+		<xsl:text>SP</xsl:text>
+
+	
+		<!-- Severity Logic:
+		     	Serious = missing privacy statement and at least one contact
+			 	Moderate = missing either privacy statement or at least one contact
+				Minor = missing only the logo
+		-->
+		<xsl:value-of select="$field_sep" />
+		<xsl:choose>
+			<xsl:when test="$privacyURL = '' and ($techContact = '' or $adminContact = '' or ($rEFEDSSecurityContact = '' and $inCommonSecurityContact = ''))">
+				<xsl:text>Significant</xsl:text>
+			</xsl:when>
+			<xsl:when test="$privacyURL = '' or ($techContact = '' or $adminContact = '' or ($rEFEDSSecurityContact = '' and $inCommonSecurityContact = ''))">
+				<xsl:text>Moderate</xsl:text>
+			</xsl:when>
+			<xsl:when test="$logo = ''">
+				<xsl:text>Minor</xsl:text>
+			</xsl:when>
+			<xsl:otherwise>
+				<xsl:text>Unknown</xsl:text>
+			</xsl:otherwise>
+		</xsl:choose>
+		
+	
+		<!-- output either mdui:privacyURL (Yes) or (No) (in that order) -->
+		<xsl:value-of select="$field_sep" />
+		<xsl:choose>
+			<xsl:when test="$privacyURL != ''">
+				<xsl:text>YES</xsl:text>
+			</xsl:when>
+			<xsl:otherwise>
+				<xsl:text>NO</xsl:text>
+			</xsl:otherwise>
+		</xsl:choose>
+
+		<!-- output Logo Present "TRUE" or "FALSE" -->
+		<xsl:value-of select="$field_sep" />
+		<xsl:choose>
+			<xsl:when test="$logo != ''">
+				<xsl:text>YES</xsl:text>
+			</xsl:when>
+			<xsl:otherwise>
+				<xsl:text>NO</xsl:text>
+			</xsl:otherwise>
+		</xsl:choose>
+
+		<!-- output "Y" (complies) or "N" (does not comply) with whether all contacts are there or not -->
+		<xsl:value-of select="$field_sep" />
+		<xsl:choose>
+			<xsl:when test="$techContact != '' and $adminContact != '' and ($rEFEDSSecurityContact != '' or $inCommonSecurityContact != '')">
+				<xsl:text>YES</xsl:text>
+			</xsl:when>
+			<xsl:otherwise>
+				<xsl:text>NO</xsl:text>
+			</xsl:otherwise>
+		</xsl:choose>
+
+		<xsl:text>&#x0a;</xsl:text>
+
+	</xsl:when>
+	<!-- <xsl:otherwise>-SP-</xsl:otherwise> -->
+	</xsl:choose>
+
+	</xsl:template>
+
+	<!--
+		A named template that performs global (recursive) search-and-replace on a string
+		(similar to fn:replace(string, pattern, replace) in XSLT 2.0).
+		See: http://stackoverflow.com/questions/3067113/xslt-string-replace/3067130#3067130
+	-->
+	<xsl:template name="string-replace-all">
+		<xsl:param name="string"/>
+		<xsl:param name="search"/>
+		<xsl:param name="replace"/>
+		<xsl:choose>
+			<xsl:when test="$string = '' or $search = '' or not($search)">
+				<!-- Prevent this routine from hanging -->
+				<xsl:value-of select="$string"/>
+			</xsl:when>
+			<xsl:when test="contains($string, $search)">
+				<xsl:value-of select="substring-before($string, $search)"/>
+				<xsl:value-of select="$replace"/>
+				<xsl:call-template name="string-replace-all">
+					<xsl:with-param name="string" select="substring-after($string, $search)"/>
+					<xsl:with-param name="search" select="$search"/>
+					<xsl:with-param name="replace" select="$replace"/>
+				</xsl:call-template>
+			</xsl:when>
+			<xsl:otherwise>
+				<xsl:value-of select="$string"/>
+			</xsl:otherwise>
+		</xsl:choose>
+	</xsl:template>
+
+	<xsl:template match="text()">
+		<!-- do nothing -->
+	</xsl:template>
+</xsl:stylesheet>