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:
+33
-6
@@ -19,7 +19,7 @@
|
|||||||
/>
|
/>
|
||||||
<meta
|
<meta
|
||||||
name="keywords"
|
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 & Validator Online — Free, XSLT 1.0 / 2.0 / 3.0" />
|
<meta property="og:title" content="XSLT Tester & Validator Online — Free, XSLT 1.0 / 2.0 / 3.0" />
|
||||||
<meta
|
<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."
|
"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",
|
"@type": "Question",
|
||||||
"name": "Is XSLT Playground free to use?",
|
"name": "Is XSLT Playground free to use?",
|
||||||
@@ -275,12 +283,13 @@
|
|||||||
|
|
||||||
<!-- Static SEO content — always in DOM, indexed by crawlers, visible below the app -->
|
<!-- Static SEO content — always in DOM, indexed by crawlers, visible below the app -->
|
||||||
<section id="seo-content" aria-label="About XSLT Playground">
|
<section id="seo-content" aria-label="About XSLT Playground">
|
||||||
<h1>XSLT Online — Free XSLT Editor, Tester & Validator</h1>
|
<h1>XSLT Online — Free XSLT Editor, Tester, Viewer & Validator</h1>
|
||||||
<p>
|
<p>
|
||||||
<strong>XSLT Playground</strong> is the most powerful free online XSLT tool available.
|
<strong>XSLT Playground</strong> is the most powerful free online XSLT tool available — an
|
||||||
Unlike every other online XSLT tester, it supports <strong>XSLT 1.0, XSLT 2.0, and XSLT 3.0</strong>
|
editor, tester, viewer and validator in one. Unlike every other online XSLT tester, it supports
|
||||||
via <strong>Saxon HE 12.5</strong> — the same enterprise-grade processor used in production
|
<strong>XSLT 1.0, XSLT 2.0, and XSLT 3.0</strong> via <strong>Saxon HE 12.5</strong> — the same
|
||||||
applications worldwide. No installation, no account, no limits.
|
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>
|
</p>
|
||||||
<a class="seo-cta" href="https://xsltplayground.com/">Open the XSLT Editor →</a>
|
<a class="seo-cta" href="https://xsltplayground.com/">Open the XSLT Editor →</a>
|
||||||
<div class="version-links">
|
<div class="version-links">
|
||||||
@@ -300,6 +309,20 @@
|
|||||||
or learning the differences between the standards.
|
or learning the differences between the standards.
|
||||||
</p>
|
</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>
|
<h2>How to Use the Online XSLT Tester</h2>
|
||||||
<ol>
|
<ol>
|
||||||
<li>Open the editor at <a href="https://xsltplayground.com/">xsltplayground.com</a></li>
|
<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 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>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>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 & 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>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>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>
|
<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>
|
<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>
|
<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>
|
<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>
|
<dd>Yes, completely free. No account, no signup, no installation required. Workspaces are saved locally in your browser.</dd>
|
||||||
</dl>
|
</dl>
|
||||||
|
|||||||
@@ -115,6 +115,8 @@
|
|||||||
ul, ol { color: #374151; line-height: 1.75; padding-left: 1.5rem; }
|
ul, ol { color: #374151; line-height: 1.75; padding-left: 1.5rem; }
|
||||||
li { margin-bottom: 0.4rem; }
|
li { margin-bottom: 0.4rem; }
|
||||||
code { background: #eef2ff; padding: 0.1em 0.35em; border-radius: 3px; font-size: 0.9em; }
|
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 {
|
.feature-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
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>
|
<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>
|
</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><sales>
|
||||||
|
<sale region="EU" amount="120"/>
|
||||||
|
<sale region="US" amount="200"/>
|
||||||
|
<sale region="EU" amount="80"/>
|
||||||
|
<sale region="US" amount="50"/>
|
||||||
|
</sales></code></pre>
|
||||||
|
<p><strong>XSLT 2.0 stylesheet:</strong></p>
|
||||||
|
<pre><code><xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
|
||||||
|
<xsl:output method="xml" indent="yes"/>
|
||||||
|
<xsl:template match="/sales">
|
||||||
|
<totals>
|
||||||
|
<xsl:for-each-group select="sale" group-by="@region">
|
||||||
|
<region name="{current-grouping-key()}"
|
||||||
|
total="{sum(current-group()/@amount)}"/>
|
||||||
|
</xsl:for-each-group>
|
||||||
|
</totals>
|
||||||
|
</xsl:template>
|
||||||
|
</xsl:stylesheet></code></pre>
|
||||||
|
<p><strong>Output:</strong></p>
|
||||||
|
<pre><code><totals>
|
||||||
|
<region name="EU" total="200"/>
|
||||||
|
<region name="US" total="250"/>
|
||||||
|
</totals></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>
|
<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>
|
<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>
|
</main>
|
||||||
|
|||||||
@@ -131,6 +131,8 @@
|
|||||||
ul, ol { color: #374151; line-height: 1.75; padding-left: 1.5rem; }
|
ul, ol { color: #374151; line-height: 1.75; padding-left: 1.5rem; }
|
||||||
li { margin-bottom: 0.4rem; }
|
li { margin-bottom: 0.4rem; }
|
||||||
code { background: #eef2ff; padding: 0.1em 0.35em; border-radius: 3px; font-size: 0.9em; }
|
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 {
|
.feature-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
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>
|
<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>
|
</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><xsl:stylesheet version="3.0"
|
||||||
|
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
|
||||||
|
xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||||
|
<xsl:output method="text"/>
|
||||||
|
<xsl:template name="xsl:initial-template">
|
||||||
|
<xsl:variable name="rates" select="map{'EUR': 1.0, 'USD': 0.92, 'GBP': 1.17}"/>
|
||||||
|
<xsl:variable name="amounts" select="100, 200, 50"/>
|
||||||
|
<xsl:variable name="total" select="
|
||||||
|
fold-left($amounts, 0.0, function($acc, $n) { $acc + $n * $rates('USD') })"/>
|
||||||
|
<xsl:value-of select="format-number($total, '0.00')"/>
|
||||||
|
</xsl:template>
|
||||||
|
</xsl:stylesheet></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>
|
<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>
|
<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>
|
</main>
|
||||||
|
|||||||
@@ -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()
|
||||||
@@ -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.
|
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
|
## Common XSL use cases
|
||||||
|
|
||||||
**XML to HTML** — the most common use. An XSL stylesheet walks an XML document tree and emits HTML tags:
|
**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
|
- You need to integrate the transform into a build pipeline
|
||||||
|
|
||||||
For everything else — prototyping, debugging, sharing test cases — the online XSL tester is quicker.
|
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).
|
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+)
|
## Basic string functions (XSLT 1.0+)
|
||||||
|
|
||||||
### string-length
|
### 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.
|
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
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user