1
0
mirror of https://github.com/alexandrev/xslt-lab.git synced 2026-09-13 08:43:16 +00:00

SEO: target striking-distance keywords + strengthen landing pages and blog

Phase 1 (home): add explicit "viewer", "validate xslt online" and
"xslt transformation online" coverage in copy, meta keywords, a new
section and a FAQ entry (+ JSON-LD) to lift queries stuck at position 8-10.

Phase 2 (landing pages): add unique runnable examples with input/output
(for-each-group on /xslt-2-0/, maps + fold-left on /xslt-3-0/) plus
internal links to related blog posts and pre/code styling.

Phase 3 (blog): xsl-online-tester gets links to both version landing pages,
a FAQ block and related guides; xslt-string-functions gets a snippet-bait
summary table and related links.

Adds scripts/seo_report.py to pull GSC + GA4 metrics for tracking progress.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
alexandrev-tibco
2026-06-11 17:19:37 +02:00
parent e2fdbfd296
commit f58cb158e0
6 changed files with 357 additions and 6 deletions
+33 -6
View File
@@ -19,7 +19,7 @@
/>
<meta
name="keywords"
content="xslt online, xslt editor, xslt tester, xslt validator, xsl editor, xsl tester, xslt playground, xslt 3.0, xslt 2.0, online xslt editor, xslt transformation online, xml transformation, saxon xslt"
content="xslt online, xslt editor, xslt tester, xslt validator, xslt viewer, xsl editor, xsl tester, xsl viewer, xslt playground, xslt 3.0, xslt 2.0, online xslt editor, xslt transformation online, validate xslt online, xml transformation, saxon xslt"
/>
<meta property="og:title" content="XSLT Tester &amp; Validator Online — Free, XSLT 1.0 / 2.0 / 3.0" />
<meta
@@ -116,6 +116,14 @@
"text": "XSLT 1.0 is the original standard (1999), supported by all browsers. XSLT 2.0 (2007) adds grouping, regular expressions, multiple output documents and XPath 2.0. XSLT 3.0 (2017) adds streaming, maps, arrays, higher-order functions and JSON support. XSLT Playground supports all three versions via Saxon."
}
},
{
"@type": "Question",
"name": "Can I view and validate XSLT output online?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Yes. XSLT Playground works as an online XSLT viewer and validator: it shows the formatted transformation output with syntax highlighting and a live HTML preview, and validates your stylesheet by reporting the exact line number and Saxon error message for any problem."
}
},
{
"@type": "Question",
"name": "Is XSLT Playground free to use?",
@@ -275,12 +283,13 @@
<!-- Static SEO content — always in DOM, indexed by crawlers, visible below the app -->
<section id="seo-content" aria-label="About XSLT Playground">
<h1>XSLT Online — Free XSLT Editor, Tester &amp; Validator</h1>
<h1>XSLT Online — Free XSLT Editor, Tester, Viewer &amp; Validator</h1>
<p>
<strong>XSLT Playground</strong> is the most powerful free online XSLT tool available.
Unlike every other online XSLT tester, it supports <strong>XSLT 1.0, XSLT 2.0, and XSLT 3.0</strong>
via <strong>Saxon HE 12.5</strong> — the same enterprise-grade processor used in production
applications worldwide. No installation, no account, no limits.
<strong>XSLT Playground</strong> is the most powerful free online XSLT tool available — an
editor, tester, viewer and validator in one. Unlike every other online XSLT tester, it supports
<strong>XSLT 1.0, XSLT 2.0, and XSLT 3.0</strong> via <strong>Saxon HE 12.5</strong> — the same
enterprise-grade processor used in production applications worldwide. Run any XSLT transformation
online and see the result instantly. No installation, no account, no limits.
</p>
<a class="seo-cta" href="https://xsltplayground.com/">Open the XSLT Editor →</a>
<div class="version-links">
@@ -300,6 +309,20 @@
or learning the differences between the standards.
</p>
<h2>XSLT Viewer, Validator and Transformation Tool</h2>
<p>
XSLT Playground works as an <strong>online XSLT viewer</strong>: paste a stylesheet and an XML
document to view the formatted transformation output, with syntax highlighting and a live HTML
preview when your stylesheet uses <code>method="html"</code>. It is also a full
<strong>XSLT validator</strong> — invalid stylesheets are flagged with the exact line number and
the original Saxon error message, so you can validate XSLT online before deploying it to production.
</p>
<p>
Every <strong>XSLT transformation online</strong> runs against a real Saxon backend in
milliseconds. There is nothing to install and nothing to configure: open the editor, paste your
code, and the result appears automatically as you type.
</p>
<h2>How to Use the Online XSLT Tester</h2>
<ol>
<li>Open the editor at <a href="https://xsltplayground.com/">xsltplayground.com</a></li>
@@ -315,6 +338,7 @@
<li><strong>XSLT 3.0 support</strong> — maps, arrays, streaming, higher-order functions, JSON output. No other free online tool offers this.</li>
<li><strong>XSLT 2.0 support</strong> — grouping (<code>xsl:for-each-group</code>), regular expressions, multiple output documents, XPath 2.0.</li>
<li><strong>Real Saxon processor</strong> — Saxon HE 12.5 on the server, not a simplified browser implementation. Results match production exactly.</li>
<li><strong>Built-in XSLT validator &amp; viewer</strong> — validate stylesheets with exact line-number errors and view formatted output with a live HTML render preview.</li>
<li><strong>Multiple XML inputs</strong> — pass multiple documents as named parameters, just like in production pipelines.</li>
<li><strong>Execution trace</strong> — debug your stylesheet step by step, inspect variable values at runtime.</li>
<li><strong>Multiple workspaces</strong> — save up to 3 workspaces locally, no account needed.</li>
@@ -335,6 +359,9 @@
<dt>What is the difference between XSLT 1.0, 2.0 and 3.0?</dt>
<dd>XSLT 1.0 (1999) is the original standard. XSLT 2.0 (2007) adds grouping, regular expressions, multiple output documents, and XPath 2.0. XSLT 3.0 (2017) adds streaming, maps, arrays, higher-order functions and JSON support. XSLT Playground supports all three.</dd>
<dt>Can I view and validate XSLT output online?</dt>
<dd>Yes. XSLT Playground is an online XSLT viewer and validator: it shows formatted output with syntax highlighting and a live HTML preview, and reports the exact line number and Saxon error message when a stylesheet is invalid.</dd>
<dt>Is XSLT Playground free?</dt>
<dd>Yes, completely free. No account, no signup, no installation required. Workspaces are saved locally in your browser.</dd>
</dl>
+41
View File
@@ -115,6 +115,8 @@
ul, ol { color: #374151; line-height: 1.75; padding-left: 1.5rem; }
li { margin-bottom: 0.4rem; }
code { background: #eef2ff; padding: 0.1em 0.35em; border-radius: 3px; font-size: 0.9em; }
pre { background: #1e293b; color: #e2e8f0; padding: 1rem 1.25rem; border-radius: 8px; overflow-x: auto; line-height: 1.5; }
pre code { background: none; padding: 0; color: inherit; font-size: 0.86rem; }
.feature-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
@@ -255,6 +257,45 @@
<dd>No. Browsers only support XSLT 1.0 natively. XSLT 2.0 requires a processor like Saxon. XSLT Playground runs Saxon on the server so you can test XSLT 2.0 stylesheets without installing anything.</dd>
</dl>
<h2>Live XSLT 2.0 Example: Grouping with xsl:for-each-group</h2>
<p>
This stylesheet groups a flat list of sales by region and totals each group — a task that needs
verbose Muenchian keys in XSLT 1.0 but is trivial in XSLT 2.0. Paste it into the
<a href="/?v=2.0">XSLT 2.0 editor</a> to run it.
</p>
<p><strong>XML input:</strong></p>
<pre><code>&lt;sales&gt;
&lt;sale region="EU" amount="120"/&gt;
&lt;sale region="US" amount="200"/&gt;
&lt;sale region="EU" amount="80"/&gt;
&lt;sale region="US" amount="50"/&gt;
&lt;/sales&gt;</code></pre>
<p><strong>XSLT 2.0 stylesheet:</strong></p>
<pre><code>&lt;xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
&lt;xsl:output method="xml" indent="yes"/&gt;
&lt;xsl:template match="/sales"&gt;
&lt;totals&gt;
&lt;xsl:for-each-group select="sale" group-by="@region"&gt;
&lt;region name="{current-grouping-key()}"
total="{sum(current-group()/@amount)}"/&gt;
&lt;/xsl:for-each-group&gt;
&lt;/totals&gt;
&lt;/xsl:template&gt;
&lt;/xsl:stylesheet&gt;</code></pre>
<p><strong>Output:</strong></p>
<pre><code>&lt;totals&gt;
&lt;region name="EU" total="200"/&gt;
&lt;region name="US" total="250"/&gt;
&lt;/totals&gt;</code></pre>
<h2>Learn More About XSLT 2.0</h2>
<ul>
<li><a href="https://blog.xsltplayground.com/posts/xslt-grouping-for-each-group/">XSLT grouping with xsl:for-each-group — complete guide</a></li>
<li><a href="https://blog.xsltplayground.com/posts/xslt-result-document/">Generating multiple output files with xsl:result-document</a></li>
<li><a href="https://blog.xsltplayground.com/posts/xslt-string-functions/">XSLT string functions reference (substring, replace, tokenize)</a></li>
<li><a href="https://blog.xsltplayground.com/posts/xslt-xml-to-json-csv/">Transforming XML to JSON and CSV with XSLT</a></li>
</ul>
<h2>Also Available</h2>
<p>Looking for XSLT 3.0 with maps, arrays and higher-order functions? <a href="/xslt-3-0/">Try the XSLT 3.0 Online Tester →</a></p>
</main>
+35
View File
@@ -131,6 +131,8 @@
ul, ol { color: #374151; line-height: 1.75; padding-left: 1.5rem; }
li { margin-bottom: 0.4rem; }
code { background: #eef2ff; padding: 0.1em 0.35em; border-radius: 3px; font-size: 0.9em; }
pre { background: #1e293b; color: #e2e8f0; padding: 1rem 1.25rem; border-radius: 8px; overflow-x: auto; line-height: 1.5; }
pre code { background: none; padding: 0; color: inherit; font-size: 0.86rem; }
.feature-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
@@ -284,6 +286,39 @@
<dd>No. Browsers only support XSLT 1.0 natively. XSLT 3.0 requires a dedicated processor like Saxon. XSLT Playground runs Saxon on the server so you can test XSLT 3.0 without installing anything.</dd>
</dl>
<h2>Live XSLT 3.0 Example: Maps and Higher-Order Functions</h2>
<p>
This stylesheet builds a map of currency rates and uses the higher-order function
<code>fn:fold-left()</code> to sum a converted total — both impossible in XSLT 2.0. Paste it into
the <a href="/?v=3.0">XSLT 3.0 editor</a> to run it.
</p>
<p><strong>XSLT 3.0 stylesheet:</strong></p>
<pre><code>&lt;xsl:stylesheet version="3.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"&gt;
&lt;xsl:output method="text"/&gt;
&lt;xsl:template name="xsl:initial-template"&gt;
&lt;xsl:variable name="rates" select="map{'EUR': 1.0, 'USD': 0.92, 'GBP': 1.17}"/&gt;
&lt;xsl:variable name="amounts" select="100, 200, 50"/&gt;
&lt;xsl:variable name="total" select="
fold-left($amounts, 0.0, function($acc, $n) { $acc + $n * $rates('USD') })"/&gt;
&lt;xsl:value-of select="format-number($total, '0.00')"/&gt;
&lt;/xsl:template&gt;
&lt;/xsl:stylesheet&gt;</code></pre>
<p><strong>Output:</strong></p>
<pre><code>322.00</code></pre>
<p>
Run it with no XML input — the named <code>xsl:initial-template</code> is an XSLT 3.0 entry point
that needs no source document.
</p>
<h2>Learn More About XSLT 3.0</h2>
<ul>
<li><a href="https://blog.xsltplayground.com/posts/xslt-3-new-features/">XSLT 3.0 new features: what changed from 2.0</a></li>
<li><a href="https://blog.xsltplayground.com/posts/xslt-result-document/">Streaming and multiple outputs with xsl:result-document</a></li>
<li><a href="https://blog.xsltplayground.com/posts/xslt-xml-to-json-csv/">Transforming XML to JSON and CSV with XSLT 3.0</a></li>
</ul>
<h2>Also Available</h2>
<p>Need XSLT 2.0 with grouping and regular expressions? <a href="/xslt-2-0/">Try the XSLT 2.0 Online Tester →</a></p>
</main>
+198
View File
@@ -0,0 +1,198 @@
"""GSC + GA4 report for xsltplayground.com — last 28 days vs prior 28 days."""
import json
from datetime import date, timedelta
from google.oauth2 import service_account
import google.auth.transport.requests
import requests as req_lib
SA_KEY = json.load(open("/tmp/sa_key.json"))
SITE = "sc-domain:xsltplayground.com"
GA4_PROPERTY = "495227679" # xslt-playground (verified via admin list)
today = date.today()
end = today - timedelta(days=1)
start = end - timedelta(days=27)
prev_end = start - timedelta(days=1)
prev_start = prev_end - timedelta(days=27)
def gsc_token():
creds = service_account.Credentials.from_service_account_info(
SA_KEY, scopes=["https://www.googleapis.com/auth/webmasters.readonly"])
creds.refresh(google.auth.transport.requests.Request(session=req_lib.Session()))
return creds.token
def gsc_query(token, dims, start_d, end_d, row_limit=25, filters=None):
import urllib.parse
site = urllib.parse.quote(SITE, safe="")
url = f"https://www.googleapis.com/webmasters/v3/sites/{site}/searchAnalytics/query"
body = {
"startDate": str(start_d), "endDate": str(end_d),
"dimensions": dims, "rowLimit": row_limit,
}
if filters:
body["dimensionFilterGroups"] = filters
r = req_lib.post(url, headers={"Authorization": "Bearer " + token,
"Content-Type": "application/json"}, json=body)
if r.status_code != 200:
return {"error": f"HTTP {r.status_code}: {r.text[:200]}"}
return r.json()
def totals(rows):
c = sum(r["clicks"] for r in rows)
i = sum(r["impressions"] for r in rows)
ctr = (c / i * 100) if i else 0
pos = (sum(r["position"] * r["impressions"] for r in rows) / i) if i else 0
return c, i, ctr, pos
print("=" * 70)
print(f" GSC REPORT — {SITE}")
print(f" Current: {start}{end}")
print(f" Previous: {prev_start}{prev_end}")
print("=" * 70)
token = gsc_token()
# Site totals current vs previous (use date dimension to get all rows)
cur = gsc_query(token, ["date"], start, end, row_limit=100)
prev = gsc_query(token, ["date"], prev_start, prev_end, row_limit=100)
if "error" in cur:
print("GSC ERROR:", cur["error"])
else:
cc, ci, cctr, cpos = totals(cur.get("rows", []))
pc, pi, pctr, ppos = totals(prev.get("rows", []))
print(f"\n TOTALS (28d) current previous delta")
print(f" Clicks {cc:>10} {pc:>10} {cc-pc:+}")
print(f" Impressions {ci:>10} {pi:>10} {ci-pi:+}")
print(f" CTR {cctr:>9.2f}% {pctr:>9.2f}% {cctr-pctr:+.2f}pp")
print(f" Avg position {cpos:>10.1f} {ppos:>10.1f} {cpos-ppos:+.1f}")
# Top queries
print("\n TOP QUERIES (by impressions, current 28d)")
q = gsc_query(token, ["query"], start, end, row_limit=30)
print(f" {'query':<42} {'clk':>5} {'impr':>7} {'ctr':>6} {'pos':>5}")
for r in sorted(q.get("rows", []), key=lambda x: -x["impressions"])[:25]:
kw = r["keys"][0][:40]
print(f" {kw:<42} {r['clicks']:>5} {r['impressions']:>7} "
f"{r['ctr']*100:>5.1f}% {r['position']:>5.1f}")
# Top pages
print("\n TOP PAGES (by clicks, current 28d)")
p = gsc_query(token, ["page"], start, end, row_limit=30)
print(f" {'page':<52} {'clk':>5} {'impr':>7} {'pos':>5}")
for r in sorted(p.get("rows", []), key=lambda x: -x["clicks"])[:20]:
pg = r["keys"][0].replace("https://xsltplayground.com", "").replace(
"https://blog.xsltplayground.com", "[blog]")[:50]
print(f" {pg:<52} {r['clicks']:>5} {r['impressions']:>7} {r['position']:>5.1f}")
# High-impression low-CTR queries (opportunity)
print("\n OPPORTUNITY: high impressions, position 5-20, low CTR")
print(f" {'query':<42} {'impr':>7} {'ctr':>6} {'pos':>5}")
opps = [r for r in q.get("rows", [])
if r["impressions"] >= 30 and 4 < r["position"] <= 20 and r["ctr"] < 0.03]
for r in sorted(opps, key=lambda x: -x["impressions"])[:15]:
kw = r["keys"][0][:40]
print(f" {kw:<42} {r['impressions']:>7} {r['ctr']*100:>5.1f}% {r['position']:>5.1f}")
# Striking distance: position 8-20 (close to page 1 / top of page 1)
print("\n STRIKING DISTANCE: position 8-20 (push to top with content/links)")
print(f" {'query':<42} {'impr':>7} {'pos':>5}")
strike = [r for r in q.get("rows", [])
if r["impressions"] >= 15 and 8 <= r["position"] <= 20]
for r in sorted(strike, key=lambda x: x["position"])[:15]:
kw = r["keys"][0][:40]
print(f" {kw:<42} {r['impressions']:>7} {r['position']:>5.1f}")
# ---- GA4 ----
print("\n" + "=" * 70)
print(f" GA4 REPORT — property {GA4_PROPERTY} (xslt-playground)")
print("=" * 70)
try:
from google.analytics.data_v1beta import BetaAnalyticsDataClient
from google.analytics.data_v1beta.types import (
DateRange, Dimension, Metric, RunReportRequest, OrderBy, Filter,
FilterExpression)
ga_creds = service_account.Credentials.from_service_account_info(
SA_KEY, scopes=["https://www.googleapis.com/auth/analytics.readonly"])
client = BetaAnalyticsDataClient(credentials=ga_creds)
prop = f"properties/{GA4_PROPERTY}"
# Totals current vs previous (dateRange dimension is implicit with 2 ranges)
r = client.run_report(RunReportRequest(
property=prop,
date_ranges=[DateRange(start_date=str(start), end_date=str(end), name="cur"),
DateRange(start_date=str(prev_start), end_date=str(prev_end), name="prev")],
metrics=[Metric(name="sessions"), Metric(name="totalUsers"),
Metric(name="engagementRate"), Metric(name="averageSessionDuration"),
Metric(name="screenPageViews")],
))
vals = {}
for row in r.rows:
dr = row.dimension_values[0].value
vals[dr] = [m.value for m in row.metric_values]
cur_v = vals.get("cur", ["0"]*5)
prev_v = vals.get("prev", ["0"]*5)
labels = ["Sessions", "Users", "Engagement%", "AvgSessDur(s)", "Pageviews"]
print(f"\n {'metric':<16} {'current':>12} {'previous':>12}")
for i, lab in enumerate(labels):
cv = float(cur_v[i]); pv = float(prev_v[i])
if "%" in lab: cv*=100; pv*=100
print(f" {lab:<16} {cv:>12.1f} {pv:>12.1f}")
# Top landing pages from organic
print("\n TOP LANDING PAGES (current 28d, by sessions)")
r2 = client.run_report(RunReportRequest(
property=prop,
date_ranges=[DateRange(start_date=str(start), end_date=str(end))],
dimensions=[Dimension(name="landingPage")],
metrics=[Metric(name="sessions"), Metric(name="engagementRate"),
Metric(name="averageSessionDuration")],
order_bys=[OrderBy(metric=OrderBy.MetricOrderBy(metric_name="sessions"), desc=True)],
limit=15,
))
print(f" {'landing page':<40} {'sess':>6} {'eng%':>6} {'dur(s)':>7}")
for row in r2.rows:
lp = row.dimension_values[0].value[:38]
s = row.metric_values[0].value
e = float(row.metric_values[1].value)*100
d = float(row.metric_values[2].value)
print(f" {lp:<40} {s:>6} {e:>5.0f}% {d:>7.0f}")
# Channel breakdown
print("\n TRAFFIC BY CHANNEL (current 28d)")
r3 = client.run_report(RunReportRequest(
property=prop,
date_ranges=[DateRange(start_date=str(start), end_date=str(end))],
dimensions=[Dimension(name="sessionDefaultChannelGroup")],
metrics=[Metric(name="sessions"), Metric(name="engagementRate")],
order_bys=[OrderBy(metric=OrderBy.MetricOrderBy(metric_name="sessions"), desc=True)],
))
print(f" {'channel':<26} {'sess':>6} {'eng%':>6}")
for row in r3.rows:
ch = row.dimension_values[0].value[:24]
s = row.metric_values[0].value
e = float(row.metric_values[1].value)*100
print(f" {ch:<26} {s:>6} {e:>5.0f}%")
# Key events / conversions
print("\n TOP EVENTS (current 28d)")
r4 = client.run_report(RunReportRequest(
property=prop,
date_ranges=[DateRange(start_date=str(start), end_date=str(end))],
dimensions=[Dimension(name="eventName")],
metrics=[Metric(name="eventCount")],
order_bys=[OrderBy(metric=OrderBy.MetricOrderBy(metric_name="eventCount"), desc=True)],
limit=15,
))
print(f" {'event':<32} {'count':>8}")
for row in r4.rows:
ev = row.dimension_values[0].value[:30]
c = row.metric_values[0].value
print(f" {ev:<32} {c:>8}")
except Exception as e:
import traceback
print(" GA4 error:", str(e)[:300])
traceback.print_exc()
+22
View File
@@ -27,6 +27,8 @@ In practice, when people say "XSL" in an integration or development context, the
The output appears immediately. If the stylesheet has errors, the error panel shows the exact line and message from Saxon.
If you already know which version you need, there are dedicated testers: the [XSLT 2.0 online tester](https://xsltplayground.com/xslt-2-0/) for grouping and regular expressions, and the [XSLT 3.0 online tester](https://xsltplayground.com/xslt-3-0/) for maps, arrays and streaming. Both run the same Saxon HE backend.
## Common XSL use cases
**XML to HTML** — the most common use. An XSL stylesheet walks an XML document tree and emits HTML tags:
@@ -96,3 +98,23 @@ For most development and debugging tasks, the online tester is faster than runni
- You need to integrate the transform into a build pipeline
For everything else — prototyping, debugging, sharing test cases — the online XSL tester is quicker.
## Frequently asked questions
**Is there a free XSL online tester?**
Yes. [XSLT Playground](https://xsltplayground.com) is a completely free online XSL tester. It runs XSLT 1.0, 2.0 and 3.0 on a real Saxon HE backend, with no account, signup or installation required.
**Can I run XSL transformations in the browser?**
The editor runs in your browser, but the transformation itself executes on a Saxon server so the results match production exactly — unlike the browser's built-in XSLT 1.0 engine, which is limited and inconsistent across browsers.
**What is the difference between .xsl and .xslt files?**
Both extensions are valid and interchangeable. `.xsl` is older and common in enterprise systems; `.xslt` is more explicit. Saxon and XSLT Playground accept either.
**Can I validate an XSL stylesheet online?**
Yes. If your stylesheet is malformed or has a runtime error, the [XSLT validator](https://blog.xsltplayground.com/posts/xslt-validator-online/) reports the exact line number and the original Saxon error message so you can fix it before deploying.
## Related guides
- [XSLT online editor: how to test transformations without installing anything](https://blog.xsltplayground.com/posts/xslt-online-editor-guide/)
- [XSLT for beginners: your first transformation](https://blog.xsltplayground.com/posts/xslt-for-beginners/)
- [XSLT validator online: catch errors before running your transform](https://blog.xsltplayground.com/posts/xslt-validator-online/)
@@ -7,6 +7,28 @@ tags: ["xslt", "xpath", "strings", "functions"]
String manipulation is one of the most common tasks in XSLT. Whether you are formatting output, parsing codes, or normalising values from external systems, XPath provides a rich set of string functions. This reference covers the most useful ones with examples you can run in [XSLT Playground](https://xsltplayground.com).
## XSLT string functions at a glance
| Function | Purpose | Minimum version |
|---|---|---|
| `string-length` | Number of characters in a string | XSLT 1.0 |
| `substring` | Extract part of a string by position | XSLT 1.0 |
| `substring-before` / `substring-after` | Split a string on a delimiter | XSLT 1.0 |
| `contains` / `starts-with` | Test for a substring or prefix | XSLT 1.0 |
| `concat` | Join strings together | XSLT 1.0 |
| `normalize-space` | Trim and collapse whitespace | XSLT 1.0 |
| `translate` | Replace characters one-for-one | XSLT 1.0 |
| `upper-case` / `lower-case` | Change case | XSLT 2.0 |
| `ends-with` | Test for a suffix | XSLT 2.0 |
| `replace` | Regex-based substitution | XSLT 2.0 |
| `matches` | Test a string against a regex | XSLT 2.0 |
| `tokenize` | Split a string into a sequence by regex | XSLT 2.0 |
| `string-join` | Join a sequence with a separator | XSLT 2.0 |
| `format-number` | Format a number with a picture pattern | XSLT 1.0 |
| `format-date` / `format-dateTime` | Format dates with a picture string | XSLT 2.0 |
Each function is explained with runnable examples below. Try them in the [XSLT 2.0 online tester](https://xsltplayground.com/xslt-2-0/) for the 2.0 functions, or the main [online XSLT editor](https://xsltplayground.com) for the 1.0 ones.
## Basic string functions (XSLT 1.0+)
### string-length
@@ -179,3 +201,9 @@ Format xs:date and xs:dateTime values using picture strings.
All of these work in [XSLT Playground](https://xsltplayground.com). Set the version to 2.0 or 3.0 for the functions that require it.
## Related guides
- [XSLT grouping with xsl:for-each-group](https://blog.xsltplayground.com/posts/xslt-grouping-for-each-group/) — group and aggregate string values
- [Transforming XML to JSON and CSV with XSLT](https://blog.xsltplayground.com/posts/xslt-xml-to-json-csv/) — `string-join` and `tokenize` in practice
- [XSLT template matching explained](https://blog.xsltplayground.com/posts/xslt-template-matching-explained/) — apply string logic inside template rules