1
0
mirror of https://github.com/zerobig/vscode-1c-metadata-viewer.git synced 2024-11-21 17:56:31 +02:00

0.1.0 Предпросмотр форм

This commit is contained in:
Ilya Bushin 2024-03-06 16:31:00 +03:00
parent a42186394d
commit 97d0000246
11 changed files with 759 additions and 9 deletions

View File

@ -1,6 +1,9 @@
# Changelog
## 0.0.11
## 0.1.0
* [new] Предпросмотр форм
## 0.0.12
* [new] Общие картинки и стили
* [fix] Ошибка когда конфигурация или расширение поддерживает несколько языков

View File

@ -8,10 +8,9 @@
![Скриншот дерева метаданных](/resources/screenshot_0.png)
**ВНИМАНИЕ! Следующий функционал находится в статусе 'beta'! Предоставлен исключительно в ознакомительных целях.**
* Открывает табличные документы в режиме просмотра.
* Открывает формы объектов и табличные документы в режиме просмотра.
![Скриншот предпросмотра формы](/resources/screenshot_2.png)
![Скриншот табличного документа](/resources/screenshot_1.png)
## Метаданные и модули

View File

@ -2,7 +2,7 @@
"name": "vscode-1c-metadata-viewer",
"displayName": "1C Metadata Viewer",
"description": "Explore 1C:Enterprise 8 configuration in the usual way",
"version": "0.0.12",
"version": "0.1.0",
"publisher": "zerobig",
"license": "MIT",
"engines": {
@ -66,6 +66,10 @@
"command": "metadataViewer.openForm",
"title": "%1c-metadata-viewer.openForm.title%"
},
{
"command": "metadataViewer.previewForm",
"title": "%1c-metadata-viewer.previewForm.title%"
},
{
"command": "metadataViewer.openModule",
"title": "%1c-metadata-viewer.openModule.title%"
@ -141,6 +145,11 @@
"group": "bsl",
"when": "viewItem == form"
},
{
"command": "metadataViewer.previewForm",
"group": "bsl",
"when": "viewItem == form"
},
{
"command": "metadataViewer.openModule",
"group": "bsl",
@ -199,7 +208,8 @@
"vscode:prepublish": "npm run compile",
"compile": "tsc -p ./",
"watch": "tsc -watch -p ./",
"lint": "eslint . --ext .ts,.tsx"
"lint": "eslint . --ext .ts,.tsx",
"precompile": "node node_modules/xslt3/xslt3.js -xsl:xslt/form.xsl -export:xslt/form.sef.json -t"
},
"devDependencies": {
"@types/node": "^16.11.7",
@ -208,11 +218,13 @@
"@typescript-eslint/eslint-plugin": "^5.30.0",
"@typescript-eslint/parser": "^5.30.0",
"eslint": "^8.13.0",
"typescript": "^4.8.4"
"typescript": "^4.8.4",
"xslt3": "^2.6.0"
},
"dependencies": {
"@vscode/webview-ui-toolkit": "^1.1.0",
"fast-glob": "^3.2.12",
"fast-xml-parser": "^4.0.11"
"fast-xml-parser": "^4.0.11",
"saxon-js": "^2.6.0"
}
}

View File

@ -5,6 +5,7 @@
"1c-metadata-viewer.openObjectModule.title": "Open object module",
"1c-metadata-viewer.openManagerModule.title": "Open manager module",
"1c-metadata-viewer.openForm.title": "Open form",
"1c-metadata-viewer.previewForm.title": "Preview form",
"1c-metadata-viewer.openModule.title": "Open module",
"1c-metadata-viewer.openCommandModule.title": "Open command module",
"1c-metadata-viewer.openRecordSetModule.title": "Open record set module",

View File

@ -5,6 +5,7 @@
"1c-metadata-viewer.openObjectModule.title": "Открыть модуль объекта",
"1c-metadata-viewer.openManagerModule.title": "Открыть модуль менеджера",
"1c-metadata-viewer.openForm.title": "Открыть форму",
"1c-metadata-viewer.previewForm.title": "Предпросмотр формы",
"1c-metadata-viewer.openModule.title": "Открыть модуль",
"1c-metadata-viewer.openCommandModule.title": "Открыть модуль команды",
"1c-metadata-viewer.openRecordSetModule.title": "Открыть модуль набора записей",

BIN
resources/screenshot_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

1
saxon-js.d.ts vendored Normal file
View File

@ -0,0 +1 @@
declare module "saxon-js";

View File

@ -3,6 +3,7 @@
import * as vscode from 'vscode';
import { MetadataView, TreeItem } from './metadataView';
import * as fs from 'fs';
import { FormPreviewer } from './formPreviewer';
export function activate(context: vscode.ExtensionContext) {
vscode.commands.registerCommand('metadataViewer.openAppModule', (node: TreeItem) => {
@ -30,6 +31,10 @@ export function activate(context: vscode.ExtensionContext) {
const filePath = node.path + '/Ext/Form/Module.bsl';
OpenFile(filePath);
});
vscode.commands.registerCommand('metadataViewer.previewForm', (node: TreeItem) => {
const filePath = node.path + '/Ext/Form.xml';
PreviewForm(filePath, context.extensionUri, node.label);
});
vscode.commands.registerCommand('metadataViewer.openModule', (node: TreeItem) => {
const filePath = node.path + '/Ext/Module.bsl';
OpenFile(filePath);
@ -80,4 +85,9 @@ function OpenFile(filePath: string) {
}
});
}
}
}
function PreviewForm(filePath: string, extensionUri: vscode.Uri, nodeDescription?: string | vscode.TreeItemLabel) {
const previewer = new FormPreviewer(filePath);
previewer.openPreview(extensionUri, nodeDescription);
}

213
src/formPreviewer.ts Normal file
View File

@ -0,0 +1,213 @@
import * as fs from "fs";
import * as vscode from "vscode";
import { XMLParser } from "fast-xml-parser";
import * as saxonJS from 'saxon-js';
export class FormPreviewer {
public static readonly viewType = "metadataViewer.formPreview";
private filePath: string;
private webpanel: vscode.WebviewPanel | undefined = undefined;
constructor(filePath: string) {
this.filePath = filePath;
}
public async openPreview(
extensionUri: vscode.Uri,
title?: string | vscode.TreeItemLabel
) {
const openPath = vscode.Uri.file(this.filePath);
if (fs.existsSync(this.filePath)) {
vscode.workspace.fs.readFile(openPath).then((configXml) => {
const parser = new XMLParser({
ignoreAttributes: false,
attributeNamePrefix: "$_",
});
let result = parser.parse(Buffer.from(configXml));
if (this.webpanel) {
// TODO:
} else {
const previewPanel = vscode.window.createWebviewPanel(
FormPreviewer.viewType,
`Предпросмотр формы (${title})`,
vscode.ViewColumn.One
);
this.webpanel = previewPanel;
}
this.generateHtml(extensionUri, Buffer.from(configXml).toString());
});
} else {
vscode.window.showInformationMessage(
`File ${this.filePath} does not exist.`
);
}
}
private generateHtml(extensionUri: vscode.Uri, xml: string) {
if (this.webpanel) {
this.generateHTMLTemplate(this.webpanel.webview, extensionUri, xml);
}
}
private generateHTMLTemplate(
webview: vscode.Webview,
extensionUri: vscode.Uri,
xml: string
) {
const sefUri = vscode.Uri.joinPath(extensionUri, "xslt", "form.sef.json");
if (!fs.existsSync(sefUri.fsPath)) {
// TODO:
return;
}
vscode.workspace.fs.readFile(sefUri).then((sef) => {
const result = saxonJS.transform(
{
stylesheetText: sef,
sourceType: "xml",
sourceText: xml,
destination: "serialized",
},
"sync"
);
let html = "principalResult" in result ? result.principalResult : "";
html = html.replace(
"<style></style>",
`
<style>
html {
font-family: arial;
font-size: 12px;
}
.window {
border: solid 1px;
max-width: 920px;
}
.window-header {
height: 22px;
background-color: #bfcddb;
}
.window-content {
margin: 10px;
}
.group {
margin: 5px 0;
border: dotted 0px #1e1e1e;
position: relative;
display: grid;
row-gap: 2px;
}
.group-caption {
display: none;
background-color: #1e1e1e;
position: absolute;
top: -8px;
left: 3px;
font-size: 11px;
}
.group-title {
color: #009690;
}
.group-content {
display: flex;
}
.group-vertical {
flex-direction: column;
}
.element {
display: inline-flex;
}
label {
white-space: pre-wrap;
}
.input {
height: 25px;
border: solid 1px #a0a0a0;
border-radius: 3px;
}
.tooltip {
color: #807a59;
}
button {
height: 26px;
border: solid 1px #a0a0a0;
border-radius: 3px;
margin: 5px;
}
.tabbed {
overflow-x: hidden; /* so we could easily hide the radio inputs */
}
.tabbed [type="radio"] {
display: none;
}
.tabs {
display: flex;
align-items: stretch;
list-style: none;
padding: 0;
}
.tab > label {
padding: 5px 10px;
border: 1px solid #a0a0a0;
border-bottom: none;
}
.tab-content {
display: none;
}
/* As we cannot replace the numbers with variables or calls to element properties, the number of this selector parts is our tab count limit */
.tabbed [type="radio"]:nth-of-type(1):checked ~ .tabs .tab:nth-of-type(1) label,
.tabbed [type="radio"]:nth-of-type(2):checked ~ .tabs .tab:nth-of-type(2) label,
.tabbed [type="radio"]:nth-of-type(3):checked ~ .tabs .tab:nth-of-type(3) label,
.tabbed [type="radio"]:nth-of-type(4):checked ~ .tabs .tab:nth-of-type(4) label,
.tabbed [type="radio"]:nth-of-type(5):checked ~ .tabs .tab:nth-of-type(5) label {
background: #37373d;
}
.tabbed [type="radio"]:nth-of-type(1):checked ~ .tab-content:nth-of-type(1),
.tabbed [type="radio"]:nth-of-type(2):checked ~ .tab-content:nth-of-type(2),
.tabbed [type="radio"]:nth-of-type(3):checked ~ .tab-content:nth-of-type(3),
.tabbed [type="radio"]:nth-of-type(4):checked ~ .tab-content:nth-of-type(4),
.tabbed [type="radio"]:nth-of-type(5):checked ~ .tab-content:nth-of-type(5) {
display: block;
}
.table-wrap {
height: 300px;
overflow: auto;
}
table, th, td {
border: 1px solid;
}
table {
width: 100%;
border-collapse: collapse;
}
thead tr th {
position: sticky;
top: 0;
background: #37373d;
}
tbody {
}
th > div,
td > div {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
</style>
`
);
html = html.replace('&lt;br /&gt;', '<br />');
webview.html = html;
});
}
}

1
xslt/form.sef.json Normal file

File diff suppressed because one or more lines are too long

509
xslt/form.xsl Normal file
View File

@ -0,0 +1,509 @@
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
version="3.0"
xpath-default-namespace="http://v8.1c.ru/8.3/xcf/logform"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:v8="http://v8.1c.ru/8.1/data/core"
xmlns:xr="http://v8.1c.ru/8.3/xcf/readable">
<xsl:output method="html" />
<xsl:variable name="lowercase" select="'abcdefghijklmnopqrstuvwxyzабвгдеёжзийклмнопрстуфхцчшщъыьэюя'" />
<xsl:variable name="uppercase" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ'" />
<xsl:variable name="digits">0123456789</xsl:variable>
<xsl:template match="/">
<html>
<head>
<style></style>
</head>
<body>
<div class="window">
<div class="window-header">
</div>
<div class="window-content">
<xsl:apply-templates select="/Form/ChildItems" />
</div>
</div>
<script></script>
</body>
</html>
</xsl:template>
<xsl:template match="ChildItems">
<xsl:for-each select="*">
<xsl:choose>
<xsl:when test="name() = 'Button'">
<xsl:apply-templates select="." />
</xsl:when>
<xsl:when test="name() = 'ButtonGroup'">
<!-- TODO: -->
</xsl:when>
<xsl:when test="name() = 'CheckBoxField'">
<xsl:apply-templates select="." />
</xsl:when>
<xsl:when test="name() = 'ColumnGroup'">
<xsl:apply-templates select="./ChildItems" />
</xsl:when>
<xsl:when test="name() = 'CommandBar'">
<xsl:apply-templates select="." />
</xsl:when>
<xsl:when test="name() = 'InputField'">
<xsl:choose>
<xsl:when test="InputField/ListChoiceMode = 'true'">
<xsl:apply-templates select="./ChoiceList" />
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="." />
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:when test="name() = 'LabelDecoration'">
<xsl:apply-templates select="." />
</xsl:when>
<xsl:when test="name() = 'LabelField'">
<xsl:apply-templates select="." />
</xsl:when>
<xsl:when test="name() = 'Pages'">
<xsl:apply-templates select="." />
</xsl:when>
<xsl:when test="name() = 'SearchStringAddition'">
<!-- TODO: -->
</xsl:when>
<xsl:when test="name() = 'Table'">
<xsl:apply-templates select="." />
</xsl:when>
<xsl:when test="name() = 'UsualGroup'">
<xsl:apply-templates select="." />
</xsl:when>
<xsl:otherwise>
<div>Обработка элемента <xsl:value-of select="name()" /> не предусмотрена!</div>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:template>
<xsl:template match="UsualGroup|CommandBar">
<div class="group">
<xsl:if test="Visible = 'false'">
<xsl:attribute name="style">
<xsl:value-of select="'display: none;'" />
</xsl:attribute>
</xsl:if>
<span class="group-caption"><xsl:value-of select="@name" /></span>
<xsl:if test="name() = 'UsualGroup' and Title and (not(ShowTitle) or ShowTitle = 'true')">
<div class="group-title">
<xsl:value-of select="Title/v8:item/v8:content/text()" />
</div>
</xsl:if>
<div>
<xsl:attribute name="class">
<xsl:choose>
<xsl:when test="Group">
<xsl:value-of select="concat('group-content', ' group-', translate(Group, $uppercase, $lowercase))" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="'group-content'" />
</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
<xsl:apply-templates select="ChildItems" />
</div>
<!-- Tooltip группы -->
<xsl:if test="ExtendedTooltip/Title">
<div>
<xsl:attribute name="class">
<xsl:value-of select="'tooltip'" />
</xsl:attribute>
<xsl:sequence
select="replace(ExtendedTooltip/Title/v8:item/v8:content/text(), '\n', '$1&lt;br /&gt;$2')"
/>
</div>
</xsl:if>
</div>
</xsl:template>
<xsl:template match="LabelDecoration">
<div class="label">
<xsl:if test="Width">
<xsl:attribute name="style">
<xsl:value-of select="concat('width: ', number(Width) * 10, 'px;')" />
</xsl:attribute>
</xsl:if>
<xsl:value-of select="Title/v8:item/v8:content/text()" />
</div>
</xsl:template>
<xsl:template match="Button">
<xsl:choose>
<xsl:when test="Type = 'Hyperlink'">
<div class="element">
<a href="#">
<xsl:choose>
<xsl:when test="Title">
<xsl:value-of select="Title/v8:item/v8:content/text()" />
</xsl:when>
<xsl:otherwise>
<!-- TODO: <xsl:evaluate xpath="CommandName/text()" /> -->
<xsl:call-template name="SplitCamelCase">
<xsl:with-param name="text" select="@name" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</a>
</div>
<xsl:if test="ExtendedTooltip/Title">
<div class="tooltip">
<xsl:value-of select="ExtendedTooltip/Title/v8:item/v8:content/text()" />
</div>
</xsl:if>
</xsl:when>
<xsl:otherwise>
<xsl:if test="not(LocationInCommandBar = 'InAdditionalSubmenu')">
<button>
<xsl:value-of select="@name" />
</button>
</xsl:if>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="InputField">
<xsl:choose>
<!-- Колонка таблицы -->
<xsl:when test="../../name() = 'Table' or ../../name() = 'ColumnGroup'">
<th>
<xsl:if test="not(Width)">
<xsl:attribute name="style">
<xsl:value-of select="'width: 100%'" />
</xsl:attribute>
</xsl:if>
<div>
<xsl:if test="Width">
<xsl:attribute name="style">
<xsl:value-of select="concat('width: ', number(Width) * 10, 'px;')" />
</xsl:attribute>
</xsl:if>
<xsl:value-of select="DataPath" />
</div>
</th>
</xsl:when>
<xsl:otherwise>
<!-- Обычное поле формы -->
<div class="element">
<xsl:if test="HorizontalStretch = 'true'">
<xsl:attribute name="style">
<xsl:value-of select="'width: 100%'" />
</xsl:attribute>
</xsl:if>
<xsl:if test="not(TitleLocation = 'None')">
<label>
<xsl:variable name="dataPath" select="DataPath"/>
<xsl:choose>
<xsl:when test="Title">
<xsl:value-of select="concat(Title/v8:item/v8:content/text(), ': ')" />
</xsl:when>
<xsl:when test="starts-with($dataPath, 'Объект.')">
<xsl:value-of select="$dataPath" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="concat(/Form/Attributes/Attribute[@name=$dataPath]/Title/v8:item/v8:content/text(), ': ')" />
</xsl:otherwise>
</xsl:choose>
</label>
</xsl:if>
<xsl:choose>
<xsl:when test="MultiLine = 'true'">
<textarea>
<xsl:if test="HorizontalStretch = 'true'">
<xsl:attribute name="style">
<xsl:value-of select="'width: 100%'" />
</xsl:attribute>
</xsl:if>
<xsl:attribute name="rows">
<xsl:value-of select="Height" />
</xsl:attribute>
</textarea>
</xsl:when>
<xsl:otherwise>
<input class="input">
<xsl:if test="InputHint">
<xsl:attribute name="placeholder">
<xsl:value-of select="InputHint/v8:item/v8:content" />
</xsl:attribute>
</xsl:if>
<xsl:choose>
<xsl:when test="Width">
<xsl:attribute name="style">
<xsl:value-of select="concat('width: ', number(Width) * 10, 'px;')" />
</xsl:attribute>
</xsl:when>
<xsl:when test="HorizontalStretch = 'true'">
<xsl:attribute name="style">
<xsl:value-of select="'width: 100%'" />
</xsl:attribute>
</xsl:when>
</xsl:choose>
</input>
</xsl:otherwise>
</xsl:choose>
</div>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="InputField/ChoiceList">
<div class="element">
<xsl:if test="not(TitleLocation = 'None')">
<label>
<xsl:value-of select="concat(../Title/v8:item/v8:content/text(), ': ')" />
</label>
</xsl:if>
<select class="input">
<xsl:apply-templates select="xr:Item" />
</select>
</div>
</xsl:template>
<xsl:template match="xr:Item">
<option>
<xsl:attribute name="value">
<xsl:value-of select="xr:Value/Value" />
</xsl:attribute>
<xsl:value-of select="xr:Value/Presentation/v8:item/v8:content" />
</option>
</xsl:template>
<xsl:template match="LabelField">
<th>
<xsl:if test="not(Width)">
<xsl:attribute name="style">
<xsl:value-of select="'width: 100%'" />
</xsl:attribute>
</xsl:if>
<div>
<xsl:if test="Width">
<xsl:attribute name="style">
<xsl:value-of select="concat('width: ', number(Width) * 10, 'px;')" />
</xsl:attribute>
</xsl:if>
<xsl:value-of select="DataPath" />
</div>
</th>
</xsl:template>
<xsl:template match="CheckBoxField">
<xsl:choose>
<!-- Колонка таблицы -->
<xsl:when test="../../name() = 'Table' or ../../name() = 'ColumnGroup'">
<th>
<xsl:if test="not(Width)">
<xsl:attribute name="style">
<xsl:value-of select="'width: 100%'" />
</xsl:attribute>
</xsl:if>
<div>
<xsl:if test="Width">
<xsl:attribute name="style">
<xsl:value-of select="concat('width: ', number(Width) * 10, 'px;')" />
</xsl:attribute>
</xsl:if>
<xsl:value-of select="DataPath" />
</div>
</th>
</xsl:when>
<xsl:otherwise>
<!-- Обычное поле формы -->
<div class="element">
<xsl:if test="not(TitleLocation) or TitleLocation = 'Left'">
<label>
<xsl:choose>
<xsl:when test="Title">
<xsl:value-of select="Title/v8:item/v8:content/text()" />
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="SplitCamelCase">
<xsl:with-param name="text" select="@name" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</label>
</xsl:if>
<input type="checkbox" />
<xsl:if test="TitleLocation = 'Right'">
<label>
<xsl:choose>
<xsl:when test="Title">
<xsl:value-of select="Title/v8:item/v8:content/text()" />
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="SplitCamelCase">
<xsl:with-param name="text" select="@name" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</label>
</xsl:if>
</div>
<xsl:if test="ExtendedTooltip/Title">
<div class="tooltip">
<xsl:value-of select="ExtendedTooltip/Title/v8:item/v8:content/text()" />
</div>
</xsl:if>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="Pages">
<div class="tabbed">
<xsl:for-each select="ChildItems/Page">
<xsl:call-template name="PageInput">
<xsl:with-param name="node" select="." />
<xsl:with-param name="position" select="position()" />
</xsl:call-template>
</xsl:for-each>
<ul class="tabs">
<xsl:for-each select="ChildItems/Page">
<xsl:call-template name="PageLabel">
<xsl:with-param name="node" select="." />
<xsl:with-param name="position" select="position()" />
</xsl:call-template>
</xsl:for-each>
</ul>
<xsl:for-each select="ChildItems/Page">
<xsl:call-template name="PageContent">
<xsl:with-param name="node" select="." />
</xsl:call-template>
</xsl:for-each>
</div>
</xsl:template>
<xsl:template name="PageInput">
<xsl:param name="node" />
<xsl:param name="position" />
<input type="radio">
<xsl:attribute name="id">
<xsl:value-of select="concat('tab', $node/../../@id, '_', $position)" />
</xsl:attribute>
<xsl:attribute name="name">
<xsl:value-of select="concat('tab-group', $node/../../@id)" />
</xsl:attribute>
<xsl:if test="$position = 1">
<xsl:attribute name="checked">
<xsl:value-of select="''" />
</xsl:attribute>
</xsl:if>
</input>
</xsl:template>
<xsl:template name="PageLabel">
<xsl:param name="node" />
<xsl:param name="position" />
<li class="tab">
<label>
<xsl:attribute name="for">
<xsl:value-of select="concat('tab', $node/../../@id, '_', $position)" />
</xsl:attribute>
<xsl:if test="$node/../../PagesRepresentation = 'None'">
<xsl:attribute name="style">
<xsl:value-of select="'display: none'" />
</xsl:attribute>
</xsl:if>
<xsl:value-of select="$node/Title/v8:item/v8:content/text()" />
</label>
</li>
</xsl:template>
<xsl:template name="PageContent">
<xsl:param name="node" />
<section class="tab-content">
<xsl:apply-templates select="$node/ChildItems" />
</section>
</xsl:template>
<xsl:template match="Table">
<xsl:if test="AutoCommandBar">
<xsl:apply-templates select="AutoCommandBar/ChildItems" />
</xsl:if>
<div class="table-wrap">
<table>
<thead>
<tr>
<xsl:apply-templates select="ChildItems" />
</tr>
</thead>
<tbody>
<xsl:call-template name="TableRows">
<xsl:with-param name="nodes" select="ChildItems" />
</xsl:call-template>
</tbody>
</table>
</div>
</xsl:template>
<xsl:template name="TableRows">
<xsl:param name="nodes" />
<xsl:for-each select="1 to 40">
<tr>
<xsl:for-each select="$nodes/*">
<!-- TODO: Надо обходить ещё и ColumnGroup'ы -->
<td>
<div>
<xsl:if test="Width">
<xsl:attribute name="style">
<xsl:value-of select="concat('width: ', number(Width) * 10, 'px;')" />
</xsl:attribute>
</xsl:if>
<xsl:choose>
<xsl:when test="name() = 'CheckBoxField'">
<input type="checkbox" />
</xsl:when>
<xsl:otherwise>
&#160;
</xsl:otherwise>
</xsl:choose>
</div>
</td>
</xsl:for-each>
</tr>
</xsl:for-each>
</xsl:template>
<xsl:template name="SplitCamelCase">
<xsl:param name="text" />
<xsl:param name="digitsMode" select="0" />
<xsl:param name="firstIteration" select="0" />
<xsl:if test="$text != ''">
<xsl:variable name="letter" select="substring($text, 1, 1)" />
<xsl:choose>
<xsl:when test="$firstIteration != 0 and contains($uppercase, $letter)">
<xsl:text> </xsl:text>
<xsl:value-of select="translate($letter, $uppercase, $lowercase)" />
</xsl:when>
<xsl:when test="contains($digits, $letter)">
<xsl:choose>
<xsl:when test="$digitsMode != 1">
<xsl:text> </xsl:text>
</xsl:when>
</xsl:choose>
<xsl:value-of select="$letter" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$letter"/>
</xsl:otherwise>
</xsl:choose>
<xsl:call-template name="SplitCamelCase">
<xsl:with-param name="text" select="substring-after($text, $letter)" />
<xsl:with-param name="digitsMode" select="contains($digits, $letter)" />
<xsl:with-param name="firstIteration" select="1" />
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>