Project

Profile

Help

Bug #6076 ยป csharp-names.xslt

xslt stylesheet - Vladimir Nesterovsky, 2023-06-15 13:32

 
<?xml version="1.0" encoding="utf-8"?>
<!--
This stylesheet provides functions to generate valid C# identifiers.
-->
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:map="http://www.w3.org/2005/xpath-functions/map"
xmlns:t="http://www.bphx.com/csharp"
xmlns:p="http://www.bphx.com/csharp/private/csharp-names"
xmlns="http://www.bphx.com/csharp-3.0/2009-05-23"
xpath-default-namespace="http://www.bphx.com/csharp-3.0/2009-05-23"
exclude-result-prefixes="xs map t p">

<!-- C# reserved keywords. -->
<xsl:variable name="t:csharp-reserved-words" as="xs:string+" select="
'abstract', 'as', 'base', 'bool', 'break',
'byte', 'case', 'catch', 'char', 'checked',
'class', 'const', 'continue', 'decimal', 'default',
'delegate', 'do', 'double', 'else', 'enum',
'event', 'explicit', 'extern', 'false', 'finally',
'fixed', 'float', 'for', 'foreach', 'goto',
'if', 'implicit', 'in', 'int', 'interface',
'internal', 'is', 'lock', 'long', 'namespace',
'new', 'null', 'object', 'operator', 'out',
'override', 'params', 'private', 'protected', 'public',
'readonly', 'ref', 'return', 'sbyte', 'sealed',
'short', 'sizeof', 'stackalloc', 'static', 'string',
'struct', 'switch', 'this', 'throw', 'true',
'try', 'typeof', 'uint', 'ulong', 'unchecked',
'unsafe', 'ushort', 'using', 'virtual', 'void',
'volatile', 'while'"/>

<!-- Extended reserved names. -->
<xsl:variable name="t:reserved-names" as="xs:string*">
<xsl:apply-templates mode="t:call" select="$t:reserved-names-handler"/>
</xsl:variable>

<!-- A total set of reserved words. -->
<xsl:variable name="t:reserved-words" as="xs:string*"
select="$t:csharp-reserved-words, $t:reserved-names"/>

<!-- A map of reserved words. -->
<xsl:variable name="t:reserved-words-map" as="map(xs:string, xs:string)"
select="map:merge($t:reserved-words!map{.: .})"/>

<!-- A handler to collect reserved names. -->
<xsl:variable name="t:reserved-names-handler" as="element()">
<t:reserved-names/>
</xsl:variable>

<!-- A default handler for the reserved names. -->
<xsl:template mode="t:call" match="t:reserved-names"/>

<!--
Capitalizes a name.
@value - a string to be capitalized.
Returns a capitalized version of the string.
-->
<xsl:function name="t:capitalize" as="xs:string">
<xsl:param name="value" as="xs:string?"/>

<xsl:sequence select="
concat
(
upper-case(substring($value, 1, 1)),
substring($value, 2)
)"/>
</xsl:function>

<!--
Note: though this function is borrowed from java, nevertheless
it works good in C# world.

Utility function to take a string and convert it to normal Java variable
name capitalization. This normally means converting the first
character from upper case to lower case, but in the (unusual) special
case when there is more than one character and both the first and
second characters are upper case, we leave it alone.

Thus "FooBah" becomes "fooBah" and "X" becomes "x", but "URL" stays
as "URL".

@value - a string to be decapitalized.
Returns a decapitalized version of the string.
-->
<xsl:function name="t:decapitalize" as="xs:string">
<xsl:param name="value" as="xs:string?"/>

<xsl:variable name="c" as="xs:string"
select="substring($value, 2, 1)"/>

<xsl:sequence select="
if ($c != lower-case($c)) then
$value
else
concat
(
lower-case(substring($value, 1, 1)),
substring($value, 2)
)"/>
</xsl:function>

<!--
Gets a C# name for a specified name components.
$name - a name to generate C# name for.
$default-name - a default name in case a name cannot be built.
Returns C# name (upper case first).
-->
<xsl:function name="t:create-name" as="xs:string?">
<xsl:param name="name" as="xs:string*"/>
<xsl:param name="default-name" as="xs:string?"/>

<xsl:variable name="components" as="xs:string*">
<xsl:for-each select="$name">
<xsl:analyze-string
regex="[\p{{L}}]+|\d+"
flags="imx"
select=".">
<xsl:matching-substring>
<xsl:sequence select="upper-case(substring(., 1, 1))"/>
<xsl:sequence select="lower-case(substring(., 2))"/>
</xsl:matching-substring>
</xsl:analyze-string>
</xsl:for-each>
</xsl:variable>

<xsl:sequence select="
if (empty($components)) then
$default-name
else
string-join
(
(
if ($components[1][(. le '9') and (. ge '0')]) then
(($default-name, 'N')[1], $components)
else
$components
),
''
)"/>
</xsl:function>

<!--
Allocates unique names in the form $prefix{number}?.
Note: that prefixes may coincide.
Note: that result names shall be different not only using case.
$prefixes - a name prefixes.
$names - allocated names pool.
Returns unique names.
-->
<xsl:function name="t:allocate-names" as="xs:string*">
<xsl:param name="prefixes" as="xs:string*"/>
<xsl:param name="names" as="xs:string*"/>

<xsl:if test="exists($prefixes)">
<xsl:variable name="reserved" as="map(xs:string, xs:string)"
select="map:merge($names!map{upper-case(.): .})"/>
<xsl:variable name="count" as="xs:integer" select="count($prefixes)"/>

<xsl:iterate select="$prefixes">
<xsl:param name="allocated" as="map(xs:string, xs:integer+)" select="
map:merge
(
$prefixes!map{upper-case(.): 1},
map{'duplicates': 'combine'}
)"/>

<xsl:param name="result" as="map(xs:integer, xs:string)" select="
map:merge((1 to $count)!map{.: $prefixes=>subsequence(., 1)})"/>

<xsl:on-completion select="(1 to $count)!$result(.)"/>

<xsl:variable name="position" as="xs:integer" select="position()"/>
<xsl:variable name="key" as="xs:string" select="upper-case(.)"/>

<xsl:if test="
$reserved=>map:contains($key) or
(count($allocated($key)) > 1) or
map:contains($t:reserved-words-map, .)">

<xsl:variable name="new-prefix" as="xs:string"
select="replace(., '\d+$', '')"/>
<xsl:variable name="new-key-prefix" as="xs:string"
select="upper-case($new-prefix)"/>

<xsl:variable name="name" as="xs:string">
<xsl:iterate select="1 to $count + map:size($reserved) + 1">
<xsl:variable name="new-key" as="xs:string"
select="$new-key-prefix || ."/>

<xsl:if test="
not($reserved=>map:contains($new-key)) and
not($allocated=>map:contains($new-key))">
<xsl:break select="$new-prefix || ."/>
</xsl:if>
</xsl:iterate>
</xsl:variable>

<xsl:next-iteration>
<xsl:with-param name="allocated"
select="map:put($allocated, upper-case($name), 1)"/>
<xsl:with-param name="result"
select="map:put($result, $position, $name)"/>
</xsl:next-iteration>
</xsl:if>
</xsl:iterate>
</xsl:if>
</xsl:function>

</xsl:stylesheet>
    (1-1/1)