feat: Add complete Agentic Compaction & Pipeline System
- Context Compaction System with token counting and summarization - Deterministic State Machine for flow control (no LLM decisions) - Parallel Execution Engine (up to 12 concurrent sessions) - Event-Driven Coordination via Event Bus - Agent Workspace Isolation (tools, memory, identity, files) - YAML Workflow Integration (OpenClaw/Lobster compatible) - Claude Code integration layer - Complete demo UI with real-time visualization - Comprehensive documentation and README Components: - agent-system/: Context management, token counting, subagent spawning - pipeline-system/: State machine, parallel executor, event bus, workflows - skills/: AI capabilities (LLM, ASR, TTS, VLM, image generation, etc.) - src/app/: Next.js demo application Total: ~100KB of production-ready TypeScript code
This commit is contained in:
21
skills/ASR/LICENSE.txt
Executable file
21
skills/ASR/LICENSE.txt
Executable file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 z-ai-web-dev-sdk Skills
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
580
skills/ASR/SKILL.md
Executable file
580
skills/ASR/SKILL.md
Executable file
@@ -0,0 +1,580 @@
|
||||
---
|
||||
name: ASR
|
||||
description: Implement speech-to-text (ASR/automatic speech recognition) capabilities using the z-ai-web-dev-sdk. Use this skill when the user needs to transcribe audio files, convert speech to text, build voice input features, or process audio recordings. Supports base64 encoded audio files and returns accurate text transcriptions.
|
||||
license: MIT
|
||||
---
|
||||
|
||||
# ASR (Speech to Text) Skill
|
||||
|
||||
This skill guides the implementation of speech-to-text (ASR) functionality using the z-ai-web-dev-sdk package, enabling accurate transcription of spoken audio into text.
|
||||
|
||||
## Skills Path
|
||||
|
||||
**Skill Location**: `{project_path}/skills/ASR`
|
||||
|
||||
this skill is located at above path in your project.
|
||||
|
||||
**Reference Scripts**: Example test scripts are available in the `{Skill Location}/scripts/` directory for quick testing and reference. See `{Skill Location}/scripts/asr.ts` for a working example.
|
||||
|
||||
## Overview
|
||||
|
||||
Speech-to-Text (ASR - Automatic Speech Recognition) allows you to build applications that convert spoken language in audio files into written text, enabling voice-controlled interfaces, transcription services, and audio content analysis.
|
||||
|
||||
**IMPORTANT**: z-ai-web-dev-sdk MUST be used in backend code only. Never use it in client-side code.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
The z-ai-web-dev-sdk package is already installed. Import it as shown in the examples below.
|
||||
|
||||
## CLI Usage (For Simple Tasks)
|
||||
|
||||
For simple audio transcription tasks, you can use the z-ai CLI instead of writing code. This is ideal for quick transcriptions, testing audio files, or batch processing.
|
||||
|
||||
### Basic Transcription from File
|
||||
|
||||
```bash
|
||||
# Transcribe an audio file
|
||||
z-ai asr --file ./audio.wav
|
||||
|
||||
# Save transcription to JSON file
|
||||
z-ai asr -f ./recording.mp3 -o transcript.json
|
||||
|
||||
# Transcribe and view output
|
||||
z-ai asr --file ./interview.wav --output result.json
|
||||
```
|
||||
|
||||
### Transcription from Base64
|
||||
|
||||
```bash
|
||||
# Transcribe from base64 encoded audio
|
||||
z-ai asr --base64 "UklGRiQAAABXQVZFZm10..." -o result.json
|
||||
|
||||
# Using short option
|
||||
z-ai asr -b "base64_encoded_audio_data" -o transcript.json
|
||||
```
|
||||
|
||||
### Streaming Output
|
||||
|
||||
```bash
|
||||
# Stream transcription results
|
||||
z-ai asr -f ./audio.wav --stream
|
||||
```
|
||||
|
||||
### CLI Parameters
|
||||
|
||||
- `--file, -f <path>`: **Required** (if not using --base64) - Audio file path
|
||||
- `--base64, -b <base64>`: **Required** (if not using --file) - Base64 encoded audio
|
||||
- `--output, -o <path>`: Optional - Output file path (JSON format)
|
||||
- `--stream`: Optional - Stream the transcription output
|
||||
|
||||
### Supported Audio Formats
|
||||
|
||||
The ASR service supports various audio formats including:
|
||||
- WAV (.wav)
|
||||
- MP3 (.mp3)
|
||||
- Other common audio formats
|
||||
|
||||
### When to Use CLI vs SDK
|
||||
|
||||
**Use CLI for:**
|
||||
- Quick audio file transcriptions
|
||||
- Testing audio recognition accuracy
|
||||
- Simple batch processing scripts
|
||||
- One-off transcription tasks
|
||||
|
||||
**Use SDK for:**
|
||||
- Real-time audio transcription in applications
|
||||
- Integration with recording systems
|
||||
- Custom audio processing workflows
|
||||
- Production applications with streaming audio
|
||||
|
||||
## Basic ASR Implementation
|
||||
|
||||
### Simple Audio Transcription
|
||||
|
||||
```javascript
|
||||
import ZAI from 'z-ai-web-dev-sdk';
|
||||
import fs from 'fs';
|
||||
|
||||
async function transcribeAudio(audioFilePath) {
|
||||
const zai = await ZAI.create();
|
||||
|
||||
// Read audio file and convert to base64
|
||||
const audioFile = fs.readFileSync(audioFilePath);
|
||||
const base64Audio = audioFile.toString('base64');
|
||||
|
||||
const response = await zai.audio.asr.create({
|
||||
file_base64: base64Audio
|
||||
});
|
||||
|
||||
return response.text;
|
||||
}
|
||||
|
||||
// Usage
|
||||
const transcription = await transcribeAudio('./audio.wav');
|
||||
console.log('Transcription:', transcription);
|
||||
```
|
||||
|
||||
### Transcribe Multiple Audio Files
|
||||
|
||||
```javascript
|
||||
import ZAI from 'z-ai-web-dev-sdk';
|
||||
import fs from 'fs';
|
||||
|
||||
async function transcribeBatch(audioFilePaths) {
|
||||
const zai = await ZAI.create();
|
||||
const results = [];
|
||||
|
||||
for (const filePath of audioFilePaths) {
|
||||
try {
|
||||
const audioFile = fs.readFileSync(filePath);
|
||||
const base64Audio = audioFile.toString('base64');
|
||||
|
||||
const response = await zai.audio.asr.create({
|
||||
file_base64: base64Audio
|
||||
});
|
||||
|
||||
results.push({
|
||||
file: filePath,
|
||||
success: true,
|
||||
transcription: response.text
|
||||
});
|
||||
} catch (error) {
|
||||
results.push({
|
||||
file: filePath,
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
// Usage
|
||||
const files = ['./interview1.wav', './interview2.wav', './interview3.wav'];
|
||||
const transcriptions = await transcribeBatch(files);
|
||||
|
||||
transcriptions.forEach(result => {
|
||||
if (result.success) {
|
||||
console.log(`${result.file}: ${result.transcription}`);
|
||||
} else {
|
||||
console.error(`${result.file}: Error - ${result.error}`);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Advanced Use Cases
|
||||
|
||||
### Audio File Processing with Metadata
|
||||
|
||||
```javascript
|
||||
import ZAI from 'z-ai-web-dev-sdk';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
async function transcribeWithMetadata(audioFilePath) {
|
||||
const zai = await ZAI.create();
|
||||
|
||||
// Get file metadata
|
||||
const stats = fs.statSync(audioFilePath);
|
||||
const audioFile = fs.readFileSync(audioFilePath);
|
||||
const base64Audio = audioFile.toString('base64');
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
const response = await zai.audio.asr.create({
|
||||
file_base64: base64Audio
|
||||
});
|
||||
|
||||
const endTime = Date.now();
|
||||
|
||||
return {
|
||||
filename: path.basename(audioFilePath),
|
||||
filepath: audioFilePath,
|
||||
fileSize: stats.size,
|
||||
transcription: response.text,
|
||||
wordCount: response.text.split(/\s+/).length,
|
||||
processingTime: endTime - startTime,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
}
|
||||
|
||||
// Usage
|
||||
const result = await transcribeWithMetadata('./meeting_recording.wav');
|
||||
console.log('Transcription Details:', JSON.stringify(result, null, 2));
|
||||
```
|
||||
|
||||
### Real-time Audio Processing Service
|
||||
|
||||
```javascript
|
||||
import ZAI from 'z-ai-web-dev-sdk';
|
||||
import fs from 'fs';
|
||||
|
||||
class ASRService {
|
||||
constructor() {
|
||||
this.zai = null;
|
||||
this.transcriptionCache = new Map();
|
||||
}
|
||||
|
||||
async initialize() {
|
||||
this.zai = await ZAI.create();
|
||||
}
|
||||
|
||||
generateCacheKey(audioBuffer) {
|
||||
const crypto = require('crypto');
|
||||
return crypto.createHash('md5').update(audioBuffer).digest('hex');
|
||||
}
|
||||
|
||||
async transcribe(audioFilePath, useCache = true) {
|
||||
const audioBuffer = fs.readFileSync(audioFilePath);
|
||||
const cacheKey = this.generateCacheKey(audioBuffer);
|
||||
|
||||
// Check cache
|
||||
if (useCache && this.transcriptionCache.has(cacheKey)) {
|
||||
return {
|
||||
transcription: this.transcriptionCache.get(cacheKey),
|
||||
cached: true
|
||||
};
|
||||
}
|
||||
|
||||
// Transcribe audio
|
||||
const base64Audio = audioBuffer.toString('base64');
|
||||
|
||||
const response = await this.zai.audio.asr.create({
|
||||
file_base64: base64Audio
|
||||
});
|
||||
|
||||
// Cache result
|
||||
if (useCache) {
|
||||
this.transcriptionCache.set(cacheKey, response.text);
|
||||
}
|
||||
|
||||
return {
|
||||
transcription: response.text,
|
||||
cached: false
|
||||
};
|
||||
}
|
||||
|
||||
clearCache() {
|
||||
this.transcriptionCache.clear();
|
||||
}
|
||||
|
||||
getCacheSize() {
|
||||
return this.transcriptionCache.size;
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
const asrService = new ASRService();
|
||||
await asrService.initialize();
|
||||
|
||||
const result1 = await asrService.transcribe('./audio.wav');
|
||||
console.log('First call (not cached):', result1);
|
||||
|
||||
const result2 = await asrService.transcribe('./audio.wav');
|
||||
console.log('Second call (cached):', result2);
|
||||
```
|
||||
|
||||
### Directory Transcription
|
||||
|
||||
```javascript
|
||||
import ZAI from 'z-ai-web-dev-sdk';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
async function transcribeDirectory(directoryPath, outputJsonPath) {
|
||||
const zai = await ZAI.create();
|
||||
|
||||
// Get all audio files
|
||||
const files = fs.readdirSync(directoryPath);
|
||||
const audioFiles = files.filter(file =>
|
||||
/\.(wav|mp3|m4a|flac|ogg)$/i.test(file)
|
||||
);
|
||||
|
||||
const results = {
|
||||
directory: directoryPath,
|
||||
totalFiles: audioFiles.length,
|
||||
processedAt: new Date().toISOString(),
|
||||
transcriptions: []
|
||||
};
|
||||
|
||||
for (const filename of audioFiles) {
|
||||
const filePath = path.join(directoryPath, filename);
|
||||
|
||||
try {
|
||||
const audioFile = fs.readFileSync(filePath);
|
||||
const base64Audio = audioFile.toString('base64');
|
||||
|
||||
const response = await zai.audio.asr.create({
|
||||
file_base64: base64Audio
|
||||
});
|
||||
|
||||
results.transcriptions.push({
|
||||
filename: filename,
|
||||
success: true,
|
||||
text: response.text,
|
||||
wordCount: response.text.split(/\s+/).length
|
||||
});
|
||||
|
||||
console.log(`✓ Transcribed: ${filename}`);
|
||||
} catch (error) {
|
||||
results.transcriptions.push({
|
||||
filename: filename,
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
|
||||
console.error(`✗ Failed: ${filename} - ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Save results to JSON
|
||||
fs.writeFileSync(
|
||||
outputJsonPath,
|
||||
JSON.stringify(results, null, 2)
|
||||
);
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
// Usage
|
||||
const results = await transcribeDirectory(
|
||||
'./audio-recordings',
|
||||
'./transcriptions.json'
|
||||
);
|
||||
|
||||
console.log(`\nProcessed ${results.totalFiles} files`);
|
||||
console.log(`Successful: ${results.transcriptions.filter(t => t.success).length}`);
|
||||
console.log(`Failed: ${results.transcriptions.filter(t => !t.success).length}`);
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Audio Format Handling
|
||||
|
||||
```javascript
|
||||
import ZAI from 'z-ai-web-dev-sdk';
|
||||
import fs from 'fs';
|
||||
|
||||
async function transcribeAnyFormat(audioFilePath) {
|
||||
// Supported formats: WAV, MP3, M4A, FLAC, OGG, etc.
|
||||
const validExtensions = ['.wav', '.mp3', '.m4a', '.flac', '.ogg'];
|
||||
const ext = audioFilePath.toLowerCase().substring(audioFilePath.lastIndexOf('.'));
|
||||
|
||||
if (!validExtensions.includes(ext)) {
|
||||
throw new Error(`Unsupported audio format: ${ext}`);
|
||||
}
|
||||
|
||||
const zai = await ZAI.create();
|
||||
const audioFile = fs.readFileSync(audioFilePath);
|
||||
const base64Audio = audioFile.toString('base64');
|
||||
|
||||
const response = await zai.audio.asr.create({
|
||||
file_base64: base64Audio
|
||||
});
|
||||
|
||||
return response.text;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Error Handling
|
||||
|
||||
```javascript
|
||||
import ZAI from 'z-ai-web-dev-sdk';
|
||||
import fs from 'fs';
|
||||
|
||||
async function safeTranscribe(audioFilePath) {
|
||||
try {
|
||||
// Validate file exists
|
||||
if (!fs.existsSync(audioFilePath)) {
|
||||
throw new Error(`File not found: ${audioFilePath}`);
|
||||
}
|
||||
|
||||
// Check file size (e.g., limit to 100MB)
|
||||
const stats = fs.statSync(audioFilePath);
|
||||
const fileSizeMB = stats.size / (1024 * 1024);
|
||||
|
||||
if (fileSizeMB > 100) {
|
||||
throw new Error(`File too large: ${fileSizeMB.toFixed(2)}MB (max 100MB)`);
|
||||
}
|
||||
|
||||
// Transcribe
|
||||
const zai = await ZAI.create();
|
||||
const audioFile = fs.readFileSync(audioFilePath);
|
||||
const base64Audio = audioFile.toString('base64');
|
||||
|
||||
const response = await zai.audio.asr.create({
|
||||
file_base64: base64Audio
|
||||
});
|
||||
|
||||
if (!response.text || response.text.trim().length === 0) {
|
||||
throw new Error('Empty transcription result');
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
transcription: response.text,
|
||||
filePath: audioFilePath,
|
||||
fileSize: stats.size
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Transcription error:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
filePath: audioFilePath
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Post-Processing Transcriptions
|
||||
|
||||
```javascript
|
||||
function cleanTranscription(text) {
|
||||
// Remove excessive whitespace
|
||||
text = text.replace(/\s+/g, ' ').trim();
|
||||
|
||||
// Capitalize first letter of sentences
|
||||
text = text.replace(/(^\w|[.!?]\s+\w)/g, match => match.toUpperCase());
|
||||
|
||||
// Remove filler words (optional)
|
||||
const fillers = ['um', 'uh', 'ah', 'like', 'you know'];
|
||||
const fillerPattern = new RegExp(`\\b(${fillers.join('|')})\\b`, 'gi');
|
||||
text = text.replace(fillerPattern, '').replace(/\s+/g, ' ');
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
async function transcribeAndClean(audioFilePath) {
|
||||
const zai = await ZAI.create();
|
||||
|
||||
const audioFile = fs.readFileSync(audioFilePath);
|
||||
const base64Audio = audioFile.toString('base64');
|
||||
|
||||
const response = await zai.audio.asr.create({
|
||||
file_base64: base64Audio
|
||||
});
|
||||
|
||||
return {
|
||||
raw: response.text,
|
||||
cleaned: cleanTranscription(response.text)
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Common Use Cases
|
||||
|
||||
1. **Meeting Transcription**: Convert recorded meetings into searchable text
|
||||
2. **Interview Processing**: Transcribe interviews for analysis and documentation
|
||||
3. **Podcast Transcription**: Create text versions of podcast episodes
|
||||
4. **Voice Notes**: Convert voice memos to text for easier reference
|
||||
5. **Call Center Analytics**: Analyze customer service calls
|
||||
6. **Accessibility**: Provide text alternatives for audio content
|
||||
7. **Voice Commands**: Enable voice-controlled applications
|
||||
8. **Language Learning**: Transcribe pronunciation practice
|
||||
|
||||
## Integration Examples
|
||||
|
||||
### Express.js API Endpoint
|
||||
|
||||
```javascript
|
||||
import express from 'express';
|
||||
import multer from 'multer';
|
||||
import ZAI from 'z-ai-web-dev-sdk';
|
||||
import fs from 'fs';
|
||||
|
||||
const app = express();
|
||||
const upload = multer({ dest: 'uploads/' });
|
||||
|
||||
let zaiInstance;
|
||||
|
||||
async function initZAI() {
|
||||
zaiInstance = await ZAI.create();
|
||||
}
|
||||
|
||||
app.post('/api/transcribe', upload.single('audio'), async (req, res) => {
|
||||
try {
|
||||
if (!req.file) {
|
||||
return res.status(400).json({ error: 'No audio file provided' });
|
||||
}
|
||||
|
||||
const audioFile = fs.readFileSync(req.file.path);
|
||||
const base64Audio = audioFile.toString('base64');
|
||||
|
||||
const response = await zaiInstance.audio.asr.create({
|
||||
file_base64: base64Audio
|
||||
});
|
||||
|
||||
// Clean up uploaded file
|
||||
fs.unlinkSync(req.file.path);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
transcription: response.text,
|
||||
wordCount: response.text.split(/\s+/).length
|
||||
});
|
||||
} catch (error) {
|
||||
// Clean up on error
|
||||
if (req.file && fs.existsSync(req.file.path)) {
|
||||
fs.unlinkSync(req.file.path);
|
||||
}
|
||||
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
initZAI().then(() => {
|
||||
app.listen(3000, () => {
|
||||
console.log('ASR API running on port 3000');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Issue**: "SDK must be used in backend"
|
||||
- **Solution**: Ensure z-ai-web-dev-sdk is only imported in server-side code
|
||||
|
||||
**Issue**: Empty or incorrect transcription
|
||||
- **Solution**: Verify audio quality and format. Check if audio contains clear speech
|
||||
|
||||
**Issue**: Large file processing fails
|
||||
- **Solution**: Consider splitting large audio files into smaller segments
|
||||
|
||||
**Issue**: Slow transcription speed
|
||||
- **Solution**: Implement caching for repeated transcriptions, optimize file sizes
|
||||
|
||||
**Issue**: Memory errors with large files
|
||||
- **Solution**: Process files in chunks or increase Node.js memory limit
|
||||
|
||||
## Performance Tips
|
||||
|
||||
1. **Reuse SDK Instance**: Create once, use multiple times
|
||||
2. **Implement Caching**: Cache transcriptions for duplicate files
|
||||
3. **Batch Processing**: Process multiple files efficiently with proper queuing
|
||||
4. **Audio Optimization**: Compress audio files before processing when possible
|
||||
5. **Async Operations**: Use Promise.all for parallel processing when appropriate
|
||||
|
||||
## Audio Quality Guidelines
|
||||
|
||||
For best transcription results:
|
||||
- **Sample Rate**: 16kHz or higher
|
||||
- **Format**: WAV, MP3, or M4A recommended
|
||||
- **Noise Level**: Minimize background noise
|
||||
- **Speech Clarity**: Clear pronunciation and normal speaking pace
|
||||
- **File Size**: Under 100MB recommended for individual files
|
||||
|
||||
## Remember
|
||||
|
||||
- Always use z-ai-web-dev-sdk in backend code only
|
||||
- The SDK is already installed - import as shown in examples
|
||||
- Audio files must be converted to base64 before processing
|
||||
- Implement proper error handling for production applications
|
||||
- Consider audio quality for best transcription accuracy
|
||||
- Clean up temporary files after processing
|
||||
- Cache results for frequently transcribed files
|
||||
27
skills/ASR/scripts/asr.ts
Executable file
27
skills/ASR/scripts/asr.ts
Executable file
@@ -0,0 +1,27 @@
|
||||
import ZAI from 'z-ai-web-dev-sdk';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
async function main(inputFile: string) {
|
||||
if (!fs.existsSync(inputFile)) {
|
||||
console.error(`Audio file not found: ${inputFile}`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const zai = await ZAI.create();
|
||||
|
||||
const audioBuffer = fs.readFileSync(inputFile);
|
||||
const file_base64 = audioBuffer.toString('base64');
|
||||
|
||||
const result = await zai.audio.asr.create({ file_base64 });
|
||||
|
||||
console.log('Transcription result:');
|
||||
console.log(result.text ?? JSON.stringify(result, null, 2));
|
||||
} catch (err: any) {
|
||||
console.error('ASR failed:', err?.message || err);
|
||||
}
|
||||
}
|
||||
|
||||
main('./output.wav');
|
||||
|
||||
21
skills/LLM/LICENSE.txt
Executable file
21
skills/LLM/LICENSE.txt
Executable file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 z-ai-web-dev-sdk Skills
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
856
skills/LLM/SKILL.md
Executable file
856
skills/LLM/SKILL.md
Executable file
@@ -0,0 +1,856 @@
|
||||
---
|
||||
name: LLM
|
||||
description: Implement large language model (LLM) chat completions using the z-ai-web-dev-sdk. Use this skill when the user needs to build conversational AI applications, chatbots, AI assistants, or any text generation features. Supports multi-turn conversations, system prompts, and context management.
|
||||
license: MIT
|
||||
---
|
||||
|
||||
# LLM (Large Language Model) Skill
|
||||
|
||||
This skill guides the implementation of chat completions functionality using the z-ai-web-dev-sdk package, enabling powerful conversational AI and text generation capabilities.
|
||||
|
||||
## Skills Path
|
||||
|
||||
**Skill Location**: `{project_path}/skills/llm`
|
||||
|
||||
this skill is located at above path in your project.
|
||||
|
||||
**Reference Scripts**: Example test scripts are available in the `{Skill Location}/scripts/` directory for quick testing and reference. See `{Skill Location}/scripts/chat.ts` for a working example.
|
||||
|
||||
## Overview
|
||||
|
||||
The LLM skill allows you to build applications that leverage large language models for natural language understanding and generation, including chatbots, AI assistants, content generation, and more.
|
||||
|
||||
**IMPORTANT**: z-ai-web-dev-sdk MUST be used in backend code only. Never use it in client-side code.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
The z-ai-web-dev-sdk package is already installed. Import it as shown in the examples below.
|
||||
|
||||
## CLI Usage (For Simple Tasks)
|
||||
|
||||
For simple, one-off chat completions, you can use the z-ai CLI instead of writing code. This is ideal for quick tests, simple queries, or automation scripts.
|
||||
|
||||
### Basic Chat
|
||||
|
||||
```bash
|
||||
# Simple question
|
||||
z-ai chat --prompt "What is the capital of France?"
|
||||
|
||||
# Save response to file
|
||||
z-ai chat -p "Explain quantum computing" -o response.json
|
||||
|
||||
# Stream the response
|
||||
z-ai chat -p "Write a short poem" --stream
|
||||
```
|
||||
|
||||
### With System Prompt
|
||||
|
||||
```bash
|
||||
# Custom system prompt for specific behavior
|
||||
z-ai chat \
|
||||
--prompt "Review this code: function add(a,b) { return a+b; }" \
|
||||
--system "You are an expert code reviewer" \
|
||||
-o review.json
|
||||
```
|
||||
|
||||
### With Thinking (Chain of Thought)
|
||||
|
||||
```bash
|
||||
# Enable thinking for complex reasoning
|
||||
z-ai chat \
|
||||
--prompt "Solve this math problem: If a train travels 120km in 2 hours, what's its speed?" \
|
||||
--thinking \
|
||||
-o solution.json
|
||||
```
|
||||
|
||||
### CLI Parameters
|
||||
|
||||
- `--prompt, -p <text>`: **Required** - User message content
|
||||
- `--system, -s <text>`: Optional - System prompt for custom behavior
|
||||
- `--thinking, -t`: Optional - Enable chain-of-thought reasoning (default: disabled)
|
||||
- `--output, -o <path>`: Optional - Output file path (JSON format)
|
||||
- `--stream`: Optional - Stream the response in real-time
|
||||
|
||||
### When to Use CLI vs SDK
|
||||
|
||||
**Use CLI for:**
|
||||
- Quick one-off questions
|
||||
- Simple automation scripts
|
||||
- Testing prompts
|
||||
- Single-turn conversations
|
||||
|
||||
**Use SDK for:**
|
||||
- Multi-turn conversations with context
|
||||
- Custom conversation management
|
||||
- Integration with web applications
|
||||
- Complex chat workflows
|
||||
- Production applications
|
||||
|
||||
## Basic Chat Completions
|
||||
|
||||
### Simple Question and Answer
|
||||
|
||||
```javascript
|
||||
import ZAI from 'z-ai-web-dev-sdk';
|
||||
|
||||
async function askQuestion(question) {
|
||||
const zai = await ZAI.create();
|
||||
|
||||
const completion = await zai.chat.completions.create({
|
||||
messages: [
|
||||
{
|
||||
role: 'assistant',
|
||||
content: 'You are a helpful assistant.'
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: question
|
||||
}
|
||||
],
|
||||
thinking: { type: 'disabled' }
|
||||
});
|
||||
|
||||
const response = completion.choices[0]?.message?.content;
|
||||
return response;
|
||||
}
|
||||
|
||||
// Usage
|
||||
const answer = await askQuestion('What is the capital of France?');
|
||||
console.log('Answer:', answer);
|
||||
```
|
||||
|
||||
### Custom System Prompt
|
||||
|
||||
```javascript
|
||||
import ZAI from 'z-ai-web-dev-sdk';
|
||||
|
||||
async function customAssistant(systemPrompt, userMessage) {
|
||||
const zai = await ZAI.create();
|
||||
|
||||
const completion = await zai.chat.completions.create({
|
||||
messages: [
|
||||
{
|
||||
role: 'assistant',
|
||||
content: systemPrompt
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: userMessage
|
||||
}
|
||||
],
|
||||
thinking: { type: 'disabled' }
|
||||
});
|
||||
|
||||
return completion.choices[0]?.message?.content;
|
||||
}
|
||||
|
||||
// Usage - Code reviewer
|
||||
const codeReview = await customAssistant(
|
||||
'You are an expert code reviewer. Analyze code for bugs, performance issues, and best practices.',
|
||||
'Review this function: function add(a, b) { return a + b; }'
|
||||
);
|
||||
|
||||
// Usage - Creative writer
|
||||
const story = await customAssistant(
|
||||
'You are a creative fiction writer who writes engaging short stories.',
|
||||
'Write a short story about a robot learning to paint.'
|
||||
);
|
||||
|
||||
console.log(codeReview);
|
||||
console.log(story);
|
||||
```
|
||||
|
||||
## Multi-turn Conversations
|
||||
|
||||
### Conversation History Management
|
||||
|
||||
```javascript
|
||||
import ZAI from 'z-ai-web-dev-sdk';
|
||||
|
||||
class ConversationManager {
|
||||
constructor(systemPrompt = 'You are a helpful assistant.') {
|
||||
this.messages = [
|
||||
{
|
||||
role: 'assistant',
|
||||
content: systemPrompt
|
||||
}
|
||||
];
|
||||
this.zai = null;
|
||||
}
|
||||
|
||||
async initialize() {
|
||||
this.zai = await ZAI.create();
|
||||
}
|
||||
|
||||
async sendMessage(userMessage) {
|
||||
// Add user message to history
|
||||
this.messages.push({
|
||||
role: 'user',
|
||||
content: userMessage
|
||||
});
|
||||
|
||||
// Get completion
|
||||
const completion = await this.zai.chat.completions.create({
|
||||
messages: this.messages,
|
||||
thinking: { type: 'disabled' }
|
||||
});
|
||||
|
||||
const assistantResponse = completion.choices[0]?.message?.content;
|
||||
|
||||
// Add assistant response to history
|
||||
this.messages.push({
|
||||
role: 'assistant',
|
||||
content: assistantResponse
|
||||
});
|
||||
|
||||
return assistantResponse;
|
||||
}
|
||||
|
||||
getHistory() {
|
||||
return this.messages;
|
||||
}
|
||||
|
||||
clearHistory(systemPrompt = 'You are a helpful assistant.') {
|
||||
this.messages = [
|
||||
{
|
||||
role: 'assistant',
|
||||
content: systemPrompt
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
getMessageCount() {
|
||||
// Subtract 1 for system message
|
||||
return this.messages.length - 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
const conversation = new ConversationManager();
|
||||
await conversation.initialize();
|
||||
|
||||
const response1 = await conversation.sendMessage('Hi, my name is John.');
|
||||
console.log('AI:', response1);
|
||||
|
||||
const response2 = await conversation.sendMessage('What is my name?');
|
||||
console.log('AI:', response2); // Should remember the name is John
|
||||
|
||||
console.log('Total messages:', conversation.getMessageCount());
|
||||
```
|
||||
|
||||
### Context-Aware Conversations
|
||||
|
||||
```javascript
|
||||
import ZAI from 'z-ai-web-dev-sdk';
|
||||
|
||||
class ContextualChat {
|
||||
constructor() {
|
||||
this.messages = [];
|
||||
this.zai = null;
|
||||
}
|
||||
|
||||
async initialize() {
|
||||
this.zai = await ZAI.create();
|
||||
}
|
||||
|
||||
async startConversation(role, context) {
|
||||
// Set up system prompt with context
|
||||
const systemPrompt = `You are ${role}. Context: ${context}`;
|
||||
|
||||
this.messages = [
|
||||
{
|
||||
role: 'assistant',
|
||||
content: systemPrompt
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
async chat(userMessage) {
|
||||
this.messages.push({
|
||||
role: 'user',
|
||||
content: userMessage
|
||||
});
|
||||
|
||||
const completion = await this.zai.chat.completions.create({
|
||||
messages: this.messages,
|
||||
thinking: { type: 'disabled' }
|
||||
});
|
||||
|
||||
const response = completion.choices[0]?.message?.content;
|
||||
|
||||
this.messages.push({
|
||||
role: 'assistant',
|
||||
content: response
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
// Usage - Customer support scenario
|
||||
const support = new ContextualChat();
|
||||
await support.initialize();
|
||||
|
||||
await support.startConversation(
|
||||
'a customer support agent for TechCorp',
|
||||
'The user has ordered product #12345 which is delayed due to shipping issues.'
|
||||
);
|
||||
|
||||
const reply1 = await support.chat('Where is my order?');
|
||||
console.log('Support:', reply1);
|
||||
|
||||
const reply2 = await support.chat('Can I get a refund?');
|
||||
console.log('Support:', reply2);
|
||||
```
|
||||
|
||||
## Advanced Use Cases
|
||||
|
||||
### Content Generation
|
||||
|
||||
```javascript
|
||||
import ZAI from 'z-ai-web-dev-sdk';
|
||||
|
||||
class ContentGenerator {
|
||||
constructor() {
|
||||
this.zai = null;
|
||||
}
|
||||
|
||||
async initialize() {
|
||||
this.zai = await ZAI.create();
|
||||
}
|
||||
|
||||
async generateBlogPost(topic, tone = 'professional') {
|
||||
const completion = await this.zai.chat.completions.create({
|
||||
messages: [
|
||||
{
|
||||
role: 'assistant',
|
||||
content: `You are a professional content writer. Write in a ${tone} tone.`
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: `Write a blog post about: ${topic}. Include an introduction, main points, and conclusion.`
|
||||
}
|
||||
],
|
||||
thinking: { type: 'disabled' }
|
||||
});
|
||||
|
||||
return completion.choices[0]?.message?.content;
|
||||
}
|
||||
|
||||
async generateProductDescription(productName, features) {
|
||||
const completion = await this.zai.chat.completions.create({
|
||||
messages: [
|
||||
{
|
||||
role: 'assistant',
|
||||
content: 'You are an expert at writing compelling product descriptions for e-commerce.'
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: `Write a product description for "${productName}". Key features: ${features.join(', ')}.`
|
||||
}
|
||||
],
|
||||
thinking: { type: 'disabled' }
|
||||
});
|
||||
|
||||
return completion.choices[0]?.message?.content;
|
||||
}
|
||||
|
||||
async generateEmailResponse(originalEmail, intent) {
|
||||
const completion = await this.zai.chat.completions.create({
|
||||
messages: [
|
||||
{
|
||||
role: 'assistant',
|
||||
content: 'You are a professional email writer. Write clear, concise, and polite emails.'
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: `Original email: "${originalEmail}"\n\nWrite a ${intent} response.`
|
||||
}
|
||||
],
|
||||
thinking: { type: 'disabled' }
|
||||
});
|
||||
|
||||
return completion.choices[0]?.message?.content;
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
const generator = new ContentGenerator();
|
||||
await generator.initialize();
|
||||
|
||||
const blogPost = await generator.generateBlogPost(
|
||||
'The Future of Artificial Intelligence',
|
||||
'informative'
|
||||
);
|
||||
console.log('Blog Post:', blogPost);
|
||||
|
||||
const productDesc = await generator.generateProductDescription(
|
||||
'Smart Watch Pro',
|
||||
['Heart rate monitoring', 'GPS tracking', 'Waterproof', '7-day battery life']
|
||||
);
|
||||
console.log('Product Description:', productDesc);
|
||||
```
|
||||
|
||||
### Data Analysis and Summarization
|
||||
|
||||
```javascript
|
||||
import ZAI from 'z-ai-web-dev-sdk';
|
||||
|
||||
async function analyzeData(data, analysisType) {
|
||||
const zai = await ZAI.create();
|
||||
|
||||
const prompts = {
|
||||
summarize: 'You are a data analyst. Summarize the key insights from the data.',
|
||||
trend: 'You are a data analyst. Identify trends and patterns in the data.',
|
||||
recommendation: 'You are a business analyst. Provide actionable recommendations based on the data.'
|
||||
};
|
||||
|
||||
const completion = await zai.chat.completions.create({
|
||||
messages: [
|
||||
{
|
||||
role: 'assistant',
|
||||
content: prompts[analysisType] || prompts.summarize
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: `Analyze this data:\n\n${JSON.stringify(data, null, 2)}`
|
||||
}
|
||||
],
|
||||
thinking: { type: 'disabled' }
|
||||
});
|
||||
|
||||
return completion.choices[0]?.message?.content;
|
||||
}
|
||||
|
||||
// Usage
|
||||
const salesData = {
|
||||
Q1: { revenue: 100000, customers: 250 },
|
||||
Q2: { revenue: 120000, customers: 280 },
|
||||
Q3: { revenue: 150000, customers: 320 },
|
||||
Q4: { revenue: 180000, customers: 380 }
|
||||
};
|
||||
|
||||
const summary = await analyzeData(salesData, 'summarize');
|
||||
const trends = await analyzeData(salesData, 'trend');
|
||||
const recommendations = await analyzeData(salesData, 'recommendation');
|
||||
|
||||
console.log('Summary:', summary);
|
||||
console.log('Trends:', trends);
|
||||
console.log('Recommendations:', recommendations);
|
||||
```
|
||||
|
||||
### Code Generation and Debugging
|
||||
|
||||
```javascript
|
||||
import ZAI from 'z-ai-web-dev-sdk';
|
||||
|
||||
class CodeAssistant {
|
||||
constructor() {
|
||||
this.zai = null;
|
||||
}
|
||||
|
||||
async initialize() {
|
||||
this.zai = await ZAI.create();
|
||||
}
|
||||
|
||||
async generateCode(description, language) {
|
||||
const completion = await this.zai.chat.completions.create({
|
||||
messages: [
|
||||
{
|
||||
role: 'assistant',
|
||||
content: `You are an expert ${language} programmer. Write clean, efficient, and well-commented code.`
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: `Write ${language} code to: ${description}`
|
||||
}
|
||||
],
|
||||
thinking: { type: 'disabled' }
|
||||
});
|
||||
|
||||
return completion.choices[0]?.message?.content;
|
||||
}
|
||||
|
||||
async debugCode(code, issue) {
|
||||
const completion = await this.zai.chat.completions.create({
|
||||
messages: [
|
||||
{
|
||||
role: 'assistant',
|
||||
content: 'You are an expert debugger. Identify bugs and suggest fixes.'
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: `Code:\n${code}\n\nIssue: ${issue}\n\nFind the bug and suggest a fix.`
|
||||
}
|
||||
],
|
||||
thinking: { type: 'disabled' }
|
||||
});
|
||||
|
||||
return completion.choices[0]?.message?.content;
|
||||
}
|
||||
|
||||
async explainCode(code) {
|
||||
const completion = await this.zai.chat.completions.create({
|
||||
messages: [
|
||||
{
|
||||
role: 'assistant',
|
||||
content: 'You are a programming teacher. Explain code clearly and simply.'
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: `Explain what this code does:\n\n${code}`
|
||||
}
|
||||
],
|
||||
thinking: { type: 'disabled' }
|
||||
});
|
||||
|
||||
return completion.choices[0]?.message?.content;
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
const codeAssist = new CodeAssistant();
|
||||
await codeAssist.initialize();
|
||||
|
||||
const newCode = await codeAssist.generateCode(
|
||||
'Create a function that sorts an array of objects by a specific property',
|
||||
'JavaScript'
|
||||
);
|
||||
console.log('Generated Code:', newCode);
|
||||
|
||||
const bugFix = await codeAssist.debugCode(
|
||||
'function add(a, b) { return a - b; }',
|
||||
'This function should add numbers but returns wrong results'
|
||||
);
|
||||
console.log('Debug Suggestion:', bugFix);
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Prompt Engineering
|
||||
|
||||
```javascript
|
||||
// Bad: Vague prompt
|
||||
const bad = await askQuestion('Tell me about AI');
|
||||
|
||||
// Good: Specific and structured prompt
|
||||
async function askWithContext(topic, format, audience) {
|
||||
const zai = await ZAI.create();
|
||||
|
||||
const completion = await zai.chat.completions.create({
|
||||
messages: [
|
||||
{
|
||||
role: 'assistant',
|
||||
content: `You are an expert educator. Explain topics clearly for ${audience}.`
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: `Explain ${topic} in ${format} format. Include practical examples.`
|
||||
}
|
||||
],
|
||||
thinking: { type: 'disabled' }
|
||||
});
|
||||
|
||||
return completion.choices[0]?.message?.content;
|
||||
}
|
||||
|
||||
const good = await askWithContext('artificial intelligence', 'bullet points', 'beginners');
|
||||
```
|
||||
|
||||
### 2. Error Handling
|
||||
|
||||
```javascript
|
||||
import ZAI from 'z-ai-web-dev-sdk';
|
||||
|
||||
async function safeCompletion(messages, retries = 3) {
|
||||
let lastError;
|
||||
|
||||
for (let attempt = 1; attempt <= retries; attempt++) {
|
||||
try {
|
||||
const zai = await ZAI.create();
|
||||
|
||||
const completion = await zai.chat.completions.create({
|
||||
messages: messages,
|
||||
thinking: { type: 'disabled' }
|
||||
});
|
||||
|
||||
const response = completion.choices[0]?.message?.content;
|
||||
|
||||
if (!response || response.trim().length === 0) {
|
||||
throw new Error('Empty response from AI');
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
content: response,
|
||||
attempts: attempt
|
||||
};
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
console.error(`Attempt ${attempt} failed:`, error.message);
|
||||
|
||||
if (attempt < retries) {
|
||||
// Wait before retry (exponential backoff)
|
||||
await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: lastError.message,
|
||||
attempts: retries
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Context Management
|
||||
|
||||
```javascript
|
||||
class ManagedConversation {
|
||||
constructor(maxMessages = 20) {
|
||||
this.maxMessages = maxMessages;
|
||||
this.systemPrompt = '';
|
||||
this.messages = [];
|
||||
this.zai = null;
|
||||
}
|
||||
|
||||
async initialize(systemPrompt) {
|
||||
this.zai = await ZAI.create();
|
||||
this.systemPrompt = systemPrompt;
|
||||
this.messages = [
|
||||
{
|
||||
role: 'assistant',
|
||||
content: systemPrompt
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
async chat(userMessage) {
|
||||
// Add user message
|
||||
this.messages.push({
|
||||
role: 'user',
|
||||
content: userMessage
|
||||
});
|
||||
|
||||
// Trim old messages if exceeding limit (keep system prompt)
|
||||
if (this.messages.length > this.maxMessages) {
|
||||
this.messages = [
|
||||
this.messages[0], // Keep system prompt
|
||||
...this.messages.slice(-(this.maxMessages - 1))
|
||||
];
|
||||
}
|
||||
|
||||
const completion = await this.zai.chat.completions.create({
|
||||
messages: this.messages,
|
||||
thinking: { type: 'disabled' }
|
||||
});
|
||||
|
||||
const response = completion.choices[0]?.message?.content;
|
||||
|
||||
this.messages.push({
|
||||
role: 'assistant',
|
||||
content: response
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
getTokenEstimate() {
|
||||
// Rough estimate: ~4 characters per token
|
||||
const totalChars = this.messages
|
||||
.map(m => m.content.length)
|
||||
.reduce((a, b) => a + b, 0);
|
||||
return Math.ceil(totalChars / 4);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Response Processing
|
||||
|
||||
```javascript
|
||||
async function getStructuredResponse(query, format = 'json') {
|
||||
const zai = await ZAI.create();
|
||||
|
||||
const formatInstructions = {
|
||||
json: 'Respond with valid JSON only. No additional text.',
|
||||
list: 'Respond with a numbered list.',
|
||||
markdown: 'Respond in Markdown format.'
|
||||
};
|
||||
|
||||
const completion = await zai.chat.completions.create({
|
||||
messages: [
|
||||
{
|
||||
role: 'assistant',
|
||||
content: `You are a helpful assistant. ${formatInstructions[format]}`
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: query
|
||||
}
|
||||
],
|
||||
thinking: { type: 'disabled' }
|
||||
});
|
||||
|
||||
const response = completion.choices[0]?.message?.content;
|
||||
|
||||
// Parse JSON if requested
|
||||
if (format === 'json') {
|
||||
try {
|
||||
return JSON.parse(response);
|
||||
} catch (e) {
|
||||
console.error('Failed to parse JSON response');
|
||||
return { raw: response };
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
// Usage
|
||||
const jsonData = await getStructuredResponse(
|
||||
'List three programming languages with their primary use cases',
|
||||
'json'
|
||||
);
|
||||
console.log(jsonData);
|
||||
```
|
||||
|
||||
## Common Use Cases
|
||||
|
||||
1. **Chatbots & Virtual Assistants**: Build conversational interfaces for customer support
|
||||
2. **Content Generation**: Create articles, product descriptions, marketing copy
|
||||
3. **Code Assistance**: Generate, explain, and debug code
|
||||
4. **Data Analysis**: Analyze and summarize complex data sets
|
||||
5. **Language Translation**: Translate text between languages
|
||||
6. **Educational Tools**: Create tutoring and learning applications
|
||||
7. **Email Automation**: Generate professional email responses
|
||||
8. **Creative Writing**: Story generation, poetry, and creative content
|
||||
|
||||
## Integration Examples
|
||||
|
||||
### Express.js Chatbot API
|
||||
|
||||
```javascript
|
||||
import express from 'express';
|
||||
import ZAI from 'z-ai-web-dev-sdk';
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
|
||||
// Store conversations in memory (use database in production)
|
||||
const conversations = new Map();
|
||||
|
||||
let zaiInstance;
|
||||
|
||||
async function initZAI() {
|
||||
zaiInstance = await ZAI.create();
|
||||
}
|
||||
|
||||
app.post('/api/chat', async (req, res) => {
|
||||
try {
|
||||
const { sessionId, message, systemPrompt } = req.body;
|
||||
|
||||
if (!message) {
|
||||
return res.status(400).json({ error: 'Message is required' });
|
||||
}
|
||||
|
||||
// Get or create conversation history
|
||||
let history = conversations.get(sessionId) || [
|
||||
{
|
||||
role: 'assistant',
|
||||
content: systemPrompt || 'You are a helpful assistant.'
|
||||
}
|
||||
];
|
||||
|
||||
// Add user message
|
||||
history.push({
|
||||
role: 'user',
|
||||
content: message
|
||||
});
|
||||
|
||||
// Get completion
|
||||
const completion = await zaiInstance.chat.completions.create({
|
||||
messages: history,
|
||||
thinking: { type: 'disabled' }
|
||||
});
|
||||
|
||||
const aiResponse = completion.choices[0]?.message?.content;
|
||||
|
||||
// Add AI response to history
|
||||
history.push({
|
||||
role: 'assistant',
|
||||
content: aiResponse
|
||||
});
|
||||
|
||||
// Save updated history
|
||||
conversations.set(sessionId, history);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
response: aiResponse,
|
||||
messageCount: history.length - 1
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
app.delete('/api/chat/:sessionId', (req, res) => {
|
||||
const { sessionId } = req.params;
|
||||
conversations.delete(sessionId);
|
||||
res.json({ success: true, message: 'Conversation cleared' });
|
||||
});
|
||||
|
||||
initZAI().then(() => {
|
||||
app.listen(3000, () => {
|
||||
console.log('Chatbot API running on port 3000');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Issue**: "SDK must be used in backend"
|
||||
- **Solution**: Ensure z-ai-web-dev-sdk is only imported and used in server-side code
|
||||
|
||||
**Issue**: Empty or incomplete responses
|
||||
- **Solution**: Check that completion.choices[0]?.message?.content exists and is not empty
|
||||
|
||||
**Issue**: Conversation context getting too long
|
||||
- **Solution**: Implement message trimming to keep only recent messages
|
||||
|
||||
**Issue**: Inconsistent responses
|
||||
- **Solution**: Use more specific system prompts and provide clear instructions
|
||||
|
||||
**Issue**: Rate limiting errors
|
||||
- **Solution**: Implement retry logic with exponential backoff
|
||||
|
||||
## Performance Tips
|
||||
|
||||
1. **Reuse SDK Instance**: Create ZAI instance once and reuse across requests
|
||||
2. **Manage Context Length**: Trim old messages to avoid token limits
|
||||
3. **Implement Caching**: Cache responses for common queries
|
||||
4. **Use Specific Prompts**: Clear prompts lead to faster, better responses
|
||||
5. **Handle Errors Gracefully**: Implement retry logic and fallback responses
|
||||
|
||||
## Security Considerations
|
||||
|
||||
1. **Input Validation**: Always validate and sanitize user input
|
||||
2. **Rate Limiting**: Implement rate limits to prevent abuse
|
||||
3. **API Key Protection**: Never expose SDK credentials in client-side code
|
||||
4. **Content Filtering**: Filter sensitive or inappropriate content
|
||||
5. **Session Management**: Implement proper session handling and cleanup
|
||||
|
||||
## Remember
|
||||
|
||||
- Always use z-ai-web-dev-sdk in backend code only
|
||||
- The SDK is already installed - import as shown in examples
|
||||
- Use the 'assistant' role for system prompts
|
||||
- Set thinking to { type: 'disabled' } for standard completions
|
||||
- Implement proper error handling and retries for production
|
||||
- Manage conversation history to avoid token limits
|
||||
- Clear and specific prompts lead to better results
|
||||
- Check `scripts/chat.ts` for a quick start example
|
||||
32
skills/LLM/scripts/chat.ts
Executable file
32
skills/LLM/scripts/chat.ts
Executable file
@@ -0,0 +1,32 @@
|
||||
import ZAI, { ChatMessage } from "z-ai-web-dev-sdk";
|
||||
|
||||
async function main(prompt: string) {
|
||||
try {
|
||||
const zai = await ZAI.create();
|
||||
|
||||
const messages: ChatMessage[] = [
|
||||
{
|
||||
role: "assistant",
|
||||
content: "Hi, I'm a helpful assistant."
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: prompt,
|
||||
},
|
||||
];
|
||||
|
||||
const response = await zai.chat.completions.create({
|
||||
messages,
|
||||
stream: false,
|
||||
thinking: { type: "disabled" },
|
||||
});
|
||||
|
||||
const reply = response.choices?.[0]?.message?.content;
|
||||
console.log("Chat reply:");
|
||||
console.log(reply ?? JSON.stringify(response, null, 2));
|
||||
} catch (err: any) {
|
||||
console.error("Chat failed:", err?.message || err);
|
||||
}
|
||||
}
|
||||
|
||||
main('What is the capital of France?');
|
||||
21
skills/TTS/LICENSE.txt
Executable file
21
skills/TTS/LICENSE.txt
Executable file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 z-ai-web-dev-sdk Skills
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
735
skills/TTS/SKILL.md
Executable file
735
skills/TTS/SKILL.md
Executable file
@@ -0,0 +1,735 @@
|
||||
---
|
||||
name: TTS
|
||||
description: Implement text-to-speech (TTS) capabilities using the z-ai-web-dev-sdk. Use this skill when the user needs to convert text into natural-sounding speech, create audio content, build voice-enabled applications, or generate spoken audio files. Supports multiple voices, adjustable speed, and various audio formats.
|
||||
license: MIT
|
||||
---
|
||||
|
||||
# TTS (Text to Speech) Skill
|
||||
|
||||
This skill guides the implementation of text-to-speech (TTS) functionality using the z-ai-web-dev-sdk package, enabling conversion of text into natural-sounding speech audio.
|
||||
|
||||
## Skills Path
|
||||
|
||||
**Skill Location**: `{project_path}/skills/TTS`
|
||||
|
||||
This skill is located at the above path in your project.
|
||||
|
||||
**Reference Scripts**: Example test scripts are available in the `{Skill Location}/scripts/` directory for quick testing and reference. See `{Skill Location}/scripts/tts.ts` for a working example.
|
||||
|
||||
## Overview
|
||||
|
||||
Text-to-Speech allows you to build applications that generate spoken audio from text input, supporting various voices, speeds, and output formats for diverse use cases.
|
||||
|
||||
**IMPORTANT**: z-ai-web-dev-sdk MUST be used in backend code only. Never use it in client-side code.
|
||||
|
||||
## API Limitations and Constraints
|
||||
|
||||
Before implementing TTS functionality, be aware of these important limitations:
|
||||
|
||||
### Input Text Constraints
|
||||
- **Maximum length**: 1024 characters per request
|
||||
- Text exceeding this limit must be split into smaller chunks
|
||||
|
||||
### Audio Parameters
|
||||
- **Speed range**: 0.5 to 2.0
|
||||
- 0.5 = half speed (slower)
|
||||
- 1.0 = normal speed (default)
|
||||
- 2.0 = double speed (faster)
|
||||
- **Volume range**: Greater than 0, up to 10
|
||||
- Default: 1.0
|
||||
- Values must be greater than 0 (exclusive) and up to 10 (inclusive)
|
||||
|
||||
### Format and Streaming
|
||||
- **Streaming limitation**: When `stream: true` is enabled, only `pcm` format is supported
|
||||
- **Non-streaming**: Supports `wav`, `pcm`, and `mp3` formats
|
||||
- **Sample rate**: 24000 Hz (recommended)
|
||||
|
||||
### Best Practice for Long Text
|
||||
```javascript
|
||||
function splitTextIntoChunks(text, maxLength = 1000) {
|
||||
const chunks = [];
|
||||
const sentences = text.match(/[^.!?]+[.!?]+/g) || [text];
|
||||
|
||||
let currentChunk = '';
|
||||
for (const sentence of sentences) {
|
||||
if ((currentChunk + sentence).length <= maxLength) {
|
||||
currentChunk += sentence;
|
||||
} else {
|
||||
if (currentChunk) chunks.push(currentChunk.trim());
|
||||
currentChunk = sentence;
|
||||
}
|
||||
}
|
||||
if (currentChunk) chunks.push(currentChunk.trim());
|
||||
|
||||
return chunks;
|
||||
}
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
|
||||
The z-ai-web-dev-sdk package is already installed. Import it as shown in the examples below.
|
||||
|
||||
## CLI Usage (For Simple Tasks)
|
||||
|
||||
For simple text-to-speech conversions, you can use the z-ai CLI instead of writing code. This is ideal for quick audio generation, testing voices, or simple automation.
|
||||
|
||||
### Basic TTS
|
||||
|
||||
```bash
|
||||
# Convert text to speech (default WAV format)
|
||||
z-ai tts --input "Hello, world" --output ./hello.wav
|
||||
|
||||
# Using short options
|
||||
z-ai tts -i "Hello, world" -o ./hello.wav
|
||||
```
|
||||
|
||||
### Different Voices and Speed
|
||||
|
||||
```bash
|
||||
# Use specific voice
|
||||
z-ai tts -i "Welcome to our service" -o ./welcome.wav --voice tongtong
|
||||
|
||||
# Adjust speech speed (0.5-2.0)
|
||||
z-ai tts -i "This is faster speech" -o ./fast.wav --speed 1.5
|
||||
|
||||
# Slower speech
|
||||
z-ai tts -i "This is slower speech" -o ./slow.wav --speed 0.8
|
||||
```
|
||||
|
||||
### Different Output Formats
|
||||
|
||||
```bash
|
||||
# MP3 format
|
||||
z-ai tts -i "Hello World" -o ./hello.mp3 --format mp3
|
||||
|
||||
# WAV format (default)
|
||||
z-ai tts -i "Hello World" -o ./hello.wav --format wav
|
||||
|
||||
# PCM format
|
||||
z-ai tts -i "Hello World" -o ./hello.pcm --format pcm
|
||||
```
|
||||
|
||||
### Streaming Output
|
||||
|
||||
```bash
|
||||
# Stream audio generation
|
||||
z-ai tts -i "This is a longer text that will be streamed" -o ./stream.wav --stream
|
||||
```
|
||||
|
||||
### CLI Parameters
|
||||
|
||||
- `--input, -i <text>`: **Required** - Text to convert to speech (max 1024 characters)
|
||||
- `--output, -o <path>`: **Required** - Output audio file path
|
||||
- `--voice, -v <voice>`: Optional - Voice type (default: tongtong)
|
||||
- `--speed, -s <number>`: Optional - Speech speed, 0.5-2.0 (default: 1.0)
|
||||
- `--format, -f <format>`: Optional - Output format: wav, mp3, pcm (default: wav)
|
||||
- `--stream`: Optional - Enable streaming output (only supports pcm format)
|
||||
|
||||
### When to Use CLI vs SDK
|
||||
|
||||
**Use CLI for:**
|
||||
- Quick text-to-speech conversions
|
||||
- Testing different voices and speeds
|
||||
- Simple batch audio generation
|
||||
- Command-line automation scripts
|
||||
|
||||
**Use SDK for:**
|
||||
- Dynamic audio generation in applications
|
||||
- Integration with web services
|
||||
- Custom audio processing pipelines
|
||||
- Production applications with complex requirements
|
||||
|
||||
## Basic TTS Implementation
|
||||
|
||||
### Simple Text to Speech
|
||||
|
||||
```javascript
|
||||
import ZAI from 'z-ai-web-dev-sdk';
|
||||
import fs from 'fs';
|
||||
|
||||
async function textToSpeech(text, outputPath) {
|
||||
const zai = await ZAI.create();
|
||||
|
||||
const response = await zai.audio.tts.create({
|
||||
input: text,
|
||||
voice: 'tongtong',
|
||||
speed: 1.0,
|
||||
response_format: 'wav',
|
||||
stream: false
|
||||
});
|
||||
|
||||
// Get array buffer from Response object
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
const buffer = Buffer.from(new Uint8Array(arrayBuffer));
|
||||
|
||||
fs.writeFileSync(outputPath, buffer);
|
||||
console.log(`Audio saved to ${outputPath}`);
|
||||
return outputPath;
|
||||
}
|
||||
|
||||
// Usage
|
||||
await textToSpeech('Hello, world!', './output.wav');
|
||||
```
|
||||
|
||||
### Multiple Voice Options
|
||||
|
||||
```javascript
|
||||
import ZAI from 'z-ai-web-dev-sdk';
|
||||
import fs from 'fs';
|
||||
|
||||
async function generateWithVoice(text, voice, outputPath) {
|
||||
const zai = await ZAI.create();
|
||||
|
||||
const response = await zai.audio.tts.create({
|
||||
input: text,
|
||||
voice: voice, // Available voices: tongtong, chuichui, xiaochen, jam, kazi, douji, luodo
|
||||
speed: 1.0,
|
||||
response_format: 'wav',
|
||||
stream: false
|
||||
});
|
||||
|
||||
// Get array buffer from Response object
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
const buffer = Buffer.from(new Uint8Array(arrayBuffer));
|
||||
|
||||
fs.writeFileSync(outputPath, buffer);
|
||||
return outputPath;
|
||||
}
|
||||
|
||||
// Usage
|
||||
await generateWithVoice('Welcome to our service', 'tongtong', './welcome.wav');
|
||||
```
|
||||
|
||||
### Adjustable Speed
|
||||
|
||||
```javascript
|
||||
import ZAI from 'z-ai-web-dev-sdk';
|
||||
import fs from 'fs';
|
||||
|
||||
async function generateWithSpeed(text, speed, outputPath) {
|
||||
const zai = await ZAI.create();
|
||||
|
||||
// Speed range: 0.5 to 2.0 (API constraint)
|
||||
// 0.5 = half speed (slower)
|
||||
// 1.0 = normal speed (default)
|
||||
// 2.0 = double speed (faster)
|
||||
// Values outside this range will cause API errors
|
||||
|
||||
const response = await zai.audio.tts.create({
|
||||
input: text,
|
||||
voice: 'tongtong',
|
||||
speed: speed,
|
||||
response_format: 'wav',
|
||||
stream: false
|
||||
});
|
||||
|
||||
// Get array buffer from Response object
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
const buffer = Buffer.from(new Uint8Array(arrayBuffer));
|
||||
|
||||
fs.writeFileSync(outputPath, buffer);
|
||||
return outputPath;
|
||||
}
|
||||
|
||||
// Usage - slower narration
|
||||
await generateWithSpeed('This is an important announcement', 0.8, './slow.wav');
|
||||
|
||||
// Usage - faster narration
|
||||
await generateWithSpeed('Quick update', 1.3, './fast.wav');
|
||||
```
|
||||
|
||||
### Adjustable Volume
|
||||
|
||||
```javascript
|
||||
import ZAI from 'z-ai-web-dev-sdk';
|
||||
import fs from 'fs';
|
||||
|
||||
async function generateWithVolume(text, volume, outputPath) {
|
||||
const zai = await ZAI.create();
|
||||
|
||||
// Volume range: greater than 0, up to 10 (API constraint)
|
||||
// Values must be > 0 (exclusive) and <= 10 (inclusive)
|
||||
// Default: 1.0 (normal volume)
|
||||
|
||||
const response = await zai.audio.tts.create({
|
||||
input: text,
|
||||
voice: 'tongtong',
|
||||
speed: 1.0,
|
||||
volume: volume, // Optional parameter
|
||||
response_format: 'wav',
|
||||
stream: false
|
||||
});
|
||||
|
||||
// Get array buffer from Response object
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
const buffer = Buffer.from(new Uint8Array(arrayBuffer));
|
||||
|
||||
fs.writeFileSync(outputPath, buffer);
|
||||
return outputPath;
|
||||
}
|
||||
|
||||
// Usage - louder audio
|
||||
await generateWithVolume('This is an announcement', 5.0, './loud.wav');
|
||||
|
||||
// Usage - quieter audio
|
||||
await generateWithVolume('Whispered message', 0.5, './quiet.wav');
|
||||
```
|
||||
|
||||
## Advanced Use Cases
|
||||
|
||||
### Batch Processing
|
||||
|
||||
```javascript
|
||||
import ZAI from 'z-ai-web-dev-sdk';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
async function batchTextToSpeech(textArray, outputDir) {
|
||||
const zai = await ZAI.create();
|
||||
const results = [];
|
||||
|
||||
// Ensure output directory exists
|
||||
if (!fs.existsSync(outputDir)) {
|
||||
fs.mkdirSync(outputDir, { recursive: true });
|
||||
}
|
||||
|
||||
for (let i = 0; i < textArray.length; i++) {
|
||||
try {
|
||||
const text = textArray[i];
|
||||
const outputPath = path.join(outputDir, `audio_${i + 1}.wav`);
|
||||
|
||||
const response = await zai.audio.tts.create({
|
||||
input: text,
|
||||
voice: 'tongtong',
|
||||
speed: 1.0,
|
||||
response_format: 'wav',
|
||||
stream: false
|
||||
});
|
||||
|
||||
// Get array buffer from Response object
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
const buffer = Buffer.from(new Uint8Array(arrayBuffer));
|
||||
|
||||
fs.writeFileSync(outputPath, buffer);
|
||||
results.push({
|
||||
success: true,
|
||||
text,
|
||||
path: outputPath
|
||||
});
|
||||
} catch (error) {
|
||||
results.push({
|
||||
success: false,
|
||||
text: textArray[i],
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
// Usage
|
||||
const texts = [
|
||||
'Welcome to chapter one',
|
||||
'Welcome to chapter two',
|
||||
'Welcome to chapter three'
|
||||
];
|
||||
|
||||
const results = await batchTextToSpeech(texts, './audio-output');
|
||||
console.log('Generated:', results.length, 'audio files');
|
||||
```
|
||||
|
||||
### Dynamic Content Generation
|
||||
|
||||
```javascript
|
||||
import ZAI from 'z-ai-web-dev-sdk';
|
||||
import fs from 'fs';
|
||||
|
||||
class TTSGenerator {
|
||||
constructor() {
|
||||
this.zai = null;
|
||||
}
|
||||
|
||||
async initialize() {
|
||||
this.zai = await ZAI.create();
|
||||
}
|
||||
|
||||
async generateAudio(text, options = {}) {
|
||||
const {
|
||||
voice = 'tongtong',
|
||||
speed = 1.0,
|
||||
format = 'wav'
|
||||
} = options;
|
||||
|
||||
const response = await this.zai.audio.tts.create({
|
||||
input: text,
|
||||
voice: voice,
|
||||
speed: speed,
|
||||
response_format: format,
|
||||
stream: false
|
||||
});
|
||||
|
||||
// Get array buffer from Response object
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
return Buffer.from(new Uint8Array(arrayBuffer));
|
||||
}
|
||||
|
||||
async saveAudio(text, outputPath, options = {}) {
|
||||
const buffer = await this.generateAudio(text, options);
|
||||
if (buffer) {
|
||||
fs.writeFileSync(outputPath, buffer);
|
||||
return outputPath;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
const generator = new TTSGenerator();
|
||||
await generator.initialize();
|
||||
|
||||
await generator.saveAudio(
|
||||
'Hello, this is a test',
|
||||
'./output.wav',
|
||||
{ speed: 1.2 }
|
||||
);
|
||||
```
|
||||
|
||||
### Next.js API Route Example
|
||||
|
||||
```javascript
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
const { text, voice = 'tongtong', speed = 1.0 } = await req.json();
|
||||
|
||||
// Import ZAI SDK
|
||||
const ZAI = (await import('z-ai-web-dev-sdk')).default;
|
||||
|
||||
// Create SDK instance
|
||||
const zai = await ZAI.create();
|
||||
|
||||
// Generate TTS audio
|
||||
const response = await zai.audio.tts.create({
|
||||
input: text.trim(),
|
||||
voice: voice,
|
||||
speed: speed,
|
||||
response_format: 'wav',
|
||||
stream: false,
|
||||
});
|
||||
|
||||
// Get array buffer from Response object
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
const buffer = Buffer.from(new Uint8Array(arrayBuffer));
|
||||
|
||||
// Return audio as response
|
||||
return new NextResponse(buffer, {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': 'audio/wav',
|
||||
'Content-Length': buffer.length.toString(),
|
||||
'Cache-Control': 'no-cache',
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('TTS API Error:', error);
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: error instanceof Error ? error.message : '生成语音失败,请稍后重试',
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Text Preparation
|
||||
```javascript
|
||||
function prepareTextForTTS(text) {
|
||||
// Remove excessive whitespace
|
||||
text = text.replace(/\s+/g, ' ').trim();
|
||||
|
||||
// Expand common abbreviations for better pronunciation
|
||||
const abbreviations = {
|
||||
'Dr.': 'Doctor',
|
||||
'Mr.': 'Mister',
|
||||
'Mrs.': 'Misses',
|
||||
'etc.': 'et cetera'
|
||||
};
|
||||
|
||||
for (const [abbr, full] of Object.entries(abbreviations)) {
|
||||
text = text.replace(new RegExp(abbr, 'g'), full);
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Error Handling
|
||||
```javascript
|
||||
import ZAI from 'z-ai-web-dev-sdk';
|
||||
import fs from 'fs';
|
||||
|
||||
async function safeTTS(text, outputPath) {
|
||||
try {
|
||||
// Validate input
|
||||
if (!text || text.trim().length === 0) {
|
||||
throw new Error('Text input cannot be empty');
|
||||
}
|
||||
|
||||
if (text.length > 1024) {
|
||||
throw new Error('Text input exceeds maximum length of 1024 characters');
|
||||
}
|
||||
|
||||
const zai = await ZAI.create();
|
||||
|
||||
const response = await zai.audio.tts.create({
|
||||
input: text,
|
||||
voice: 'tongtong',
|
||||
speed: 1.0,
|
||||
response_format: 'wav',
|
||||
stream: false
|
||||
});
|
||||
|
||||
// Get array buffer from Response object
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
const buffer = Buffer.from(new Uint8Array(arrayBuffer));
|
||||
|
||||
fs.writeFileSync(outputPath, buffer);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
path: outputPath,
|
||||
size: buffer.length
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('TTS Error:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error.message
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. SDK Instance Reuse
|
||||
|
||||
```javascript
|
||||
import ZAI from 'z-ai-web-dev-sdk';
|
||||
|
||||
// Create a singleton instance
|
||||
let zaiInstance = null;
|
||||
|
||||
async function getZAIInstance() {
|
||||
if (!zaiInstance) {
|
||||
zaiInstance = await ZAI.create();
|
||||
}
|
||||
return zaiInstance;
|
||||
}
|
||||
|
||||
// Usage
|
||||
const zai = await getZAIInstance();
|
||||
const response = await zai.audio.tts.create({ ... });
|
||||
```
|
||||
|
||||
## Common Use Cases
|
||||
|
||||
1. **Audiobooks & Podcasts**: Convert written content to audio format
|
||||
2. **E-learning**: Create narration for educational content
|
||||
3. **Accessibility**: Provide audio versions of text content
|
||||
4. **Voice Assistants**: Generate dynamic responses
|
||||
5. **Announcements**: Create automated audio notifications
|
||||
6. **IVR Systems**: Generate phone system prompts
|
||||
7. **Content Localization**: Create audio in different languages
|
||||
|
||||
## Integration Examples
|
||||
|
||||
### Express.js API Endpoint
|
||||
|
||||
```javascript
|
||||
import express from 'express';
|
||||
import ZAI from 'z-ai-web-dev-sdk';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
|
||||
let zaiInstance;
|
||||
const outputDir = './audio-output';
|
||||
|
||||
async function initZAI() {
|
||||
zaiInstance = await ZAI.create();
|
||||
if (!fs.existsSync(outputDir)) {
|
||||
fs.mkdirSync(outputDir, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
app.post('/api/tts', async (req, res) => {
|
||||
try {
|
||||
const { text, voice = 'tongtong', speed = 1.0 } = req.body;
|
||||
|
||||
if (!text) {
|
||||
return res.status(400).json({ error: 'Text is required' });
|
||||
}
|
||||
|
||||
const filename = `tts_${Date.now()}.wav`;
|
||||
const outputPath = path.join(outputDir, filename);
|
||||
|
||||
const response = await zaiInstance.audio.tts.create({
|
||||
input: text,
|
||||
voice: voice,
|
||||
speed: speed,
|
||||
response_format: 'wav',
|
||||
stream: false
|
||||
});
|
||||
|
||||
// Get array buffer from Response object
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
const buffer = Buffer.from(new Uint8Array(arrayBuffer));
|
||||
|
||||
fs.writeFileSync(outputPath, buffer);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
audioUrl: `/audio/${filename}`,
|
||||
size: buffer.length
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
app.use('/audio', express.static('audio-output'));
|
||||
|
||||
initZAI().then(() => {
|
||||
app.listen(3000, () => {
|
||||
console.log('TTS API running on port 3000');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Issue**: "Input text exceeds maximum length"
|
||||
- **Solution**: Text input is limited to 1024 characters. Split longer text into chunks using the `splitTextIntoChunks` function shown in the API Limitations section
|
||||
|
||||
**Issue**: "Invalid speed parameter" or unexpected speed behavior
|
||||
- **Solution**: Speed must be between 0.5 and 2.0. Check your speed value is within this range
|
||||
|
||||
**Issue**: "Invalid volume parameter"
|
||||
- **Solution**: Volume must be greater than 0 and up to 10. Ensure volume value is in range (0, 10]
|
||||
|
||||
**Issue**: "Stream format not supported" with WAV/MP3
|
||||
- **Solution**: Streaming mode only supports PCM format. Either use `response_format: 'pcm'` with streaming, or disable streaming (`stream: false`) for WAV/MP3 output
|
||||
|
||||
**Issue**: "SDK must be used in backend"
|
||||
- **Solution**: Ensure z-ai-web-dev-sdk is only imported in server-side code
|
||||
|
||||
**Issue**: "TypeError: response.audio is undefined"
|
||||
- **Solution**: The SDK returns a standard Response object, use `await response.arrayBuffer()` instead of accessing `response.audio`
|
||||
|
||||
**Issue**: Generated audio file is empty or corrupted
|
||||
- **Solution**: Ensure you're calling `await response.arrayBuffer()` and properly converting to Buffer: `Buffer.from(new Uint8Array(arrayBuffer))`
|
||||
|
||||
**Issue**: Audio sounds unnatural
|
||||
- **Solution**: Prepare text properly (remove special characters, expand abbreviations)
|
||||
|
||||
**Issue**: Long processing times
|
||||
- **Solution**: Break long text into smaller chunks and process in parallel
|
||||
|
||||
**Issue**: Next.js caching old API route
|
||||
- **Solution**: Create a new API route endpoint or restart the dev server
|
||||
|
||||
## Performance Tips
|
||||
|
||||
1. **Reuse SDK Instance**: Create ZAI instance once and reuse
|
||||
2. **Implement Caching**: Cache generated audio for repeated text
|
||||
3. **Batch Processing**: Process multiple texts efficiently
|
||||
4. **Optimize Text**: Remove unnecessary content before generation
|
||||
5. **Async Processing**: Use queues for handling multiple requests
|
||||
|
||||
## Important Notes
|
||||
|
||||
### API Constraints
|
||||
|
||||
**Input Text Length**: Maximum 1024 characters per request. For longer text:
|
||||
```javascript
|
||||
// Split long text into chunks
|
||||
const longText = "..."; // Your long text here
|
||||
const chunks = splitTextIntoChunks(longText, 1000);
|
||||
|
||||
for (const chunk of chunks) {
|
||||
const response = await zai.audio.tts.create({
|
||||
input: chunk,
|
||||
voice: 'tongtong',
|
||||
speed: 1.0,
|
||||
response_format: 'wav',
|
||||
stream: false
|
||||
});
|
||||
// Process each chunk...
|
||||
}
|
||||
```
|
||||
|
||||
**Streaming Format Limitation**: When using `stream: true`, only `pcm` format is supported. For `wav` or `mp3` output, use `stream: false`.
|
||||
|
||||
**Sample Rate**: Audio is generated at 24000 Hz sample rate (recommended setting for playback).
|
||||
|
||||
### Response Object Format
|
||||
|
||||
The `zai.audio.tts.create()` method returns a standard **Response** object (not a custom object with an `audio` property). Always use:
|
||||
|
||||
```javascript
|
||||
// ✅ CORRECT
|
||||
const response = await zai.audio.tts.create({ ... });
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
const buffer = Buffer.from(new Uint8Array(arrayBuffer));
|
||||
|
||||
// ❌ WRONG - This will not work
|
||||
const response = await zai.audio.tts.create({ ... });
|
||||
const buffer = Buffer.from(response.audio); // response.audio is undefined
|
||||
```
|
||||
|
||||
### Available Voices
|
||||
|
||||
- `tongtong` - 温暖亲切
|
||||
- `chuichui` - 活泼可爱
|
||||
- `xiaochen` - 沉稳专业
|
||||
- `jam` - 英音绅士
|
||||
- `kazi` - 清晰标准
|
||||
- `douji` - 自然流畅
|
||||
- `luodo` - 富有感染力
|
||||
|
||||
### Speed Range
|
||||
|
||||
- Minimum: `0.5` (half speed)
|
||||
- Default: `1.0` (normal speed)
|
||||
- Maximum: `2.0` (double speed)
|
||||
|
||||
**Important**: Speed values outside the range [0.5, 2.0] will result in API errors.
|
||||
|
||||
### Volume Range
|
||||
|
||||
- Minimum: Greater than `0` (exclusive)
|
||||
- Default: `1.0` (normal volume)
|
||||
- Maximum: `10` (inclusive)
|
||||
|
||||
**Note**: Volume parameter is optional. When not specified, defaults to 1.0.
|
||||
|
||||
## Remember
|
||||
|
||||
- Always use z-ai-web-dev-sdk in backend code only
|
||||
- **Input text is limited to 1024 characters maximum** - split longer text into chunks
|
||||
- **Speed must be between 0.5 and 2.0** - values outside this range will cause errors
|
||||
- **Volume must be greater than 0 and up to 10** - optional parameter with default 1.0
|
||||
- **Streaming only supports PCM format** - use non-streaming for WAV or MP3 output
|
||||
- The SDK returns a standard Response object - use `await response.arrayBuffer()`
|
||||
- Convert ArrayBuffer to Buffer using `Buffer.from(new Uint8Array(arrayBuffer))`
|
||||
- Handle audio buffers properly when saving to files
|
||||
- Implement error handling for production applications
|
||||
- Consider caching for frequently generated content
|
||||
- Clean up old audio files periodically to manage storage
|
||||
25
skills/TTS/tts.ts
Executable file
25
skills/TTS/tts.ts
Executable file
@@ -0,0 +1,25 @@
|
||||
import ZAI from "z-ai-web-dev-sdk";
|
||||
import fs from "fs";
|
||||
|
||||
async function main(text: string, outFile: string) {
|
||||
try {
|
||||
const zai = await ZAI.create();
|
||||
|
||||
const response = await zai.audio.tts.create({
|
||||
input: text,
|
||||
voice: "tongtong",
|
||||
speed: 1.0,
|
||||
response_format: "wav",
|
||||
stream: false,
|
||||
});
|
||||
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
const buffer = Buffer.from(new Uint8Array(arrayBuffer));
|
||||
fs.writeFileSync(outFile, buffer);
|
||||
console.log(`TTS audio saved to ${outFile}`);
|
||||
} catch (err: any) {
|
||||
console.error("TTS failed:", err?.message || err);
|
||||
}
|
||||
}
|
||||
|
||||
main("Hello, world!", "./output.wav");
|
||||
21
skills/VLM/LICENSE.txt
Executable file
21
skills/VLM/LICENSE.txt
Executable file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 z-ai-web-dev-sdk Skills
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
588
skills/VLM/SKILL.md
Executable file
588
skills/VLM/SKILL.md
Executable file
@@ -0,0 +1,588 @@
|
||||
---
|
||||
name: VLM
|
||||
description: Implement vision-based AI chat capabilities using the z-ai-web-dev-sdk. Use this skill when the user needs to analyze images, describe visual content, or create applications that combine image understanding with conversational AI. Supports image URLs and base64 encoded images for multimodal interactions.
|
||||
license: MIT
|
||||
---
|
||||
|
||||
# VLM(Vision Chat) Skill
|
||||
|
||||
This skill guides the implementation of vision chat functionality using the z-ai-web-dev-sdk package, enabling AI models to understand and respond to images combined with text prompts.
|
||||
|
||||
## Skills Path
|
||||
|
||||
**Skill Location**: `{project_path}/skills/VLM`
|
||||
|
||||
this skill is located at above path in your project.
|
||||
|
||||
**Reference Scripts**: Example test scripts are available in the `{Skill Location}/scripts/` directory for quick testing and reference. See `{Skill Location}/scripts/vlm.ts` for a working example.
|
||||
|
||||
## Overview
|
||||
|
||||
Vision Chat allows you to build applications that can analyze images, extract information from visual content, and answer questions about images through natural language conversation.
|
||||
|
||||
**IMPORTANT**: z-ai-web-dev-sdk MUST be used in backend code only. Never use it in client-side code.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
The z-ai-web-dev-sdk package is already installed. Import it as shown in the examples below.
|
||||
|
||||
## CLI Usage (For Simple Tasks)
|
||||
|
||||
For simple image analysis tasks, you can use the z-ai CLI instead of writing code. This is ideal for quick image descriptions, testing vision capabilities, or simple automation.
|
||||
|
||||
### Basic Image Analysis
|
||||
|
||||
```bash
|
||||
# Describe an image from URL
|
||||
z-ai vision --prompt "What's in this image?" --image "https://example.com/photo.jpg"
|
||||
|
||||
# Using short options
|
||||
z-ai vision -p "Describe this image" -i "https://example.com/image.png"
|
||||
```
|
||||
|
||||
### Analyze Local Images
|
||||
|
||||
```bash
|
||||
# Analyze a local image file
|
||||
z-ai vision -p "What objects are in this photo?" -i "./photo.jpg"
|
||||
|
||||
# Save response to file
|
||||
z-ai vision -p "Describe the scene" -i "./landscape.png" -o description.json
|
||||
```
|
||||
|
||||
### Multiple Images
|
||||
|
||||
```bash
|
||||
# Analyze multiple images at once
|
||||
z-ai vision \
|
||||
-p "Compare these two images" \
|
||||
-i "./photo1.jpg" \
|
||||
-i "./photo2.jpg" \
|
||||
-o comparison.json
|
||||
|
||||
# Multiple images with detailed analysis
|
||||
z-ai vision \
|
||||
--prompt "What are the differences between these images?" \
|
||||
--image "https://example.com/before.jpg" \
|
||||
--image "https://example.com/after.jpg"
|
||||
```
|
||||
|
||||
### With Thinking (Chain of Thought)
|
||||
|
||||
```bash
|
||||
# Enable thinking for complex visual reasoning
|
||||
z-ai vision \
|
||||
-p "Count the number of people in this image and describe their activities" \
|
||||
-i "./crowd.jpg" \
|
||||
--thinking \
|
||||
-o analysis.json
|
||||
```
|
||||
|
||||
### Streaming Output
|
||||
|
||||
```bash
|
||||
# Stream the vision analysis
|
||||
z-ai vision -p "Describe this image in detail" -i "./photo.jpg" --stream
|
||||
```
|
||||
|
||||
### CLI Parameters
|
||||
|
||||
- `--prompt, -p <text>`: **Required** - Question or instruction about the image(s)
|
||||
- `--image, -i <URL or path>`: Optional - Image URL or local file path (can be used multiple times)
|
||||
- `--thinking, -t`: Optional - Enable chain-of-thought reasoning (default: disabled)
|
||||
- `--output, -o <path>`: Optional - Output file path (JSON format)
|
||||
- `--stream`: Optional - Stream the response in real-time
|
||||
|
||||
### Supported Image Formats
|
||||
|
||||
- PNG (.png)
|
||||
- JPEG (.jpg, .jpeg)
|
||||
- GIF (.gif)
|
||||
- WebP (.webp)
|
||||
- BMP (.bmp)
|
||||
|
||||
### When to Use CLI vs SDK
|
||||
|
||||
**Use CLI for:**
|
||||
- Quick image analysis
|
||||
- Testing vision model capabilities
|
||||
- One-off image descriptions
|
||||
- Simple automation scripts
|
||||
|
||||
**Use SDK for:**
|
||||
- Multi-turn conversations with images
|
||||
- Dynamic image analysis in applications
|
||||
- Batch processing with custom logic
|
||||
- Production applications with complex workflows
|
||||
|
||||
## Recommended Approach
|
||||
|
||||
For better performance and reliability, use base64 encoding to pass images to the model instead of image URLs.
|
||||
|
||||
## Supported Content Types
|
||||
|
||||
The Vision Chat API supports three types of media content:
|
||||
|
||||
### 1. **image_url** - For Image Files
|
||||
Use this type for static images (PNG, JPEG, GIF, WebP, etc.)
|
||||
```typescript
|
||||
{
|
||||
role: 'user',
|
||||
content: [
|
||||
{ type: 'text', text: prompt },
|
||||
{ type: 'image_url', image_url: { url: imageUrl } }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 2. **video_url** - For Video Files
|
||||
Use this type for video content (MP4, AVI, MOV, etc.)
|
||||
```typescript
|
||||
{
|
||||
role: 'user',
|
||||
content: [
|
||||
{ type: 'text', text: prompt },
|
||||
{ type: 'video_url', video_url: { url: videoUrl } }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 3. **file_url** - For Document Files
|
||||
Use this type for document files (PDF, DOCX, TXT, etc.)
|
||||
```typescript
|
||||
{
|
||||
role: 'user',
|
||||
content: [
|
||||
{ type: 'text', text: prompt },
|
||||
{ type: 'file_url', file_url: { url: fileUrl } }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Note**: You can combine multiple content types in a single message. For example, you can include both text and multiple images, or text with both an image and a document.
|
||||
|
||||
## Basic Vision Chat Implementation
|
||||
|
||||
### Single Image Analysis
|
||||
|
||||
```javascript
|
||||
import ZAI from 'z-ai-web-dev-sdk';
|
||||
|
||||
async function analyzeImage(imageUrl, question) {
|
||||
const zai = await ZAI.create();
|
||||
|
||||
const response = await zai.chat.completions.createVision({
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: question
|
||||
},
|
||||
{
|
||||
type: 'image_url',
|
||||
image_url: {
|
||||
url: imageUrl
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
thinking: { type: 'disabled' }
|
||||
});
|
||||
|
||||
return response.choices[0]?.message?.content;
|
||||
}
|
||||
|
||||
// Usage
|
||||
const result = await analyzeImage(
|
||||
'https://example.com/product.jpg',
|
||||
'Describe this product in detail'
|
||||
);
|
||||
console.log('Analysis:', result);
|
||||
```
|
||||
|
||||
### Multiple Images Analysis
|
||||
|
||||
```javascript
|
||||
import ZAI from 'z-ai-web-dev-sdk';
|
||||
|
||||
async function compareImages(imageUrls, question) {
|
||||
const zai = await ZAI.create();
|
||||
|
||||
const content = [
|
||||
{
|
||||
type: 'text',
|
||||
text: question
|
||||
},
|
||||
...imageUrls.map(url => ({
|
||||
type: 'image_url',
|
||||
image_url: { url }
|
||||
}))
|
||||
];
|
||||
|
||||
const response = await zai.chat.completions.createVision({
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: content
|
||||
}
|
||||
],
|
||||
thinking: { type: 'disabled' }
|
||||
});
|
||||
|
||||
return response.choices[0]?.message?.content;
|
||||
}
|
||||
|
||||
// Usage
|
||||
const comparison = await compareImages(
|
||||
[
|
||||
'https://example.com/before.jpg',
|
||||
'https://example.com/after.jpg'
|
||||
],
|
||||
'Compare these two images and describe the differences'
|
||||
);
|
||||
```
|
||||
|
||||
### Base64 Image Support
|
||||
|
||||
```javascript
|
||||
import ZAI from 'z-ai-web-dev-sdk';
|
||||
import fs from 'fs';
|
||||
|
||||
async function analyzeLocalImage(imagePath, question) {
|
||||
const zai = await ZAI.create();
|
||||
|
||||
// Read image file and convert to base64
|
||||
const imageBuffer = fs.readFileSync(imagePath);
|
||||
const base64Image = imageBuffer.toString('base64');
|
||||
const mimeType = imagePath.endsWith('.png') ? 'image/png' : 'image/jpeg';
|
||||
|
||||
const response = await zai.chat.completions.createVision({
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: question
|
||||
},
|
||||
{
|
||||
type: 'image_url',
|
||||
image_url: {
|
||||
url: `data:${mimeType};base64,${base64Image}`
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
thinking: { type: 'disabled' }
|
||||
});
|
||||
|
||||
return response.choices[0]?.message?.content;
|
||||
}
|
||||
```
|
||||
|
||||
## Advanced Use Cases
|
||||
|
||||
### Conversational Vision Chat
|
||||
|
||||
```javascript
|
||||
import ZAI from 'z-ai-web-dev-sdk';
|
||||
|
||||
class VisionChatSession {
|
||||
constructor() {
|
||||
this.messages = [];
|
||||
}
|
||||
|
||||
async initialize() {
|
||||
this.zai = await ZAI.create();
|
||||
}
|
||||
|
||||
async addImage(imageUrl, initialQuestion) {
|
||||
this.messages.push({
|
||||
role: 'user',
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: initialQuestion
|
||||
},
|
||||
{
|
||||
type: 'image_url',
|
||||
image_url: { url: imageUrl }
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
return this.getResponse();
|
||||
}
|
||||
|
||||
async followUp(question) {
|
||||
this.messages.push({
|
||||
role: 'user',
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: question
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
return this.getResponse();
|
||||
}
|
||||
|
||||
async getResponse() {
|
||||
const response = await this.zai.chat.completions.createVision({
|
||||
messages: this.messages,
|
||||
thinking: { type: 'disabled' }
|
||||
});
|
||||
|
||||
const assistantMessage = response.choices[0]?.message?.content;
|
||||
|
||||
this.messages.push({
|
||||
role: 'assistant',
|
||||
content: assistantMessage
|
||||
});
|
||||
|
||||
return assistantMessage;
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
const session = new VisionChatSession();
|
||||
await session.initialize();
|
||||
|
||||
const initial = await session.addImage(
|
||||
'https://example.com/chart.jpg',
|
||||
'What does this chart show?'
|
||||
);
|
||||
console.log('Initial analysis:', initial);
|
||||
|
||||
const followup = await session.followUp('What are the key trends?');
|
||||
console.log('Follow-up:', followup);
|
||||
```
|
||||
|
||||
### Image Classification and Tagging
|
||||
|
||||
```javascript
|
||||
import ZAI from 'z-ai-web-dev-sdk';
|
||||
|
||||
async function classifyImage(imageUrl) {
|
||||
const zai = await ZAI.create();
|
||||
|
||||
const prompt = `Analyze this image and provide:
|
||||
1. Main subject/category
|
||||
2. Key objects detected
|
||||
3. Scene description
|
||||
4. Suggested tags (comma-separated)
|
||||
|
||||
Format your response as JSON.`;
|
||||
|
||||
const response = await zai.chat.completions.createVision({
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: prompt
|
||||
},
|
||||
{
|
||||
type: 'image_url',
|
||||
image_url: { url: imageUrl }
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
thinking: { type: 'disabled' }
|
||||
});
|
||||
|
||||
const content = response.choices[0]?.message?.content;
|
||||
|
||||
try {
|
||||
return JSON.parse(content);
|
||||
} catch (e) {
|
||||
return { rawResponse: content };
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### OCR and Text Extraction
|
||||
|
||||
```javascript
|
||||
import ZAI from 'z-ai-web-dev-sdk';
|
||||
|
||||
async function extractText(imageUrl) {
|
||||
const zai = await ZAI.create();
|
||||
|
||||
const response = await zai.chat.completions.createVision({
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Extract all text from this image. Preserve the layout and formatting as much as possible.'
|
||||
},
|
||||
{
|
||||
type: 'image_url',
|
||||
image_url: { url: imageUrl }
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
thinking: { type: 'disabled' }
|
||||
});
|
||||
|
||||
return response.choices[0]?.message?.content;
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Image Quality and Size
|
||||
- Use high-quality images for better analysis results
|
||||
- Optimize image size to balance quality and processing speed
|
||||
- Supported formats: JPEG, PNG, WebP
|
||||
|
||||
### 2. Prompt Engineering
|
||||
- Be specific about what information you need from the image
|
||||
- Structure complex requests with numbered lists or bullet points
|
||||
- Provide context about the image type (photo, diagram, chart, etc.)
|
||||
|
||||
### 3. Error Handling
|
||||
```javascript
|
||||
async function safeVisionChat(imageUrl, question) {
|
||||
try {
|
||||
const zai = await ZAI.create();
|
||||
|
||||
const response = await zai.chat.completions.createVision({
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: [
|
||||
{ type: 'text', text: question },
|
||||
{ type: 'image_url', image_url: { url: imageUrl } }
|
||||
]
|
||||
}
|
||||
],
|
||||
thinking: { type: 'disabled' }
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
content: response.choices[0]?.message?.content
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Vision chat error:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error.message
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Performance Optimization
|
||||
- Cache SDK instance creation when processing multiple images
|
||||
- Use appropriate image formats (JPEG for photos, PNG for diagrams)
|
||||
- Consider image preprocessing for large batches
|
||||
|
||||
### 5. Security Considerations
|
||||
- Validate image URLs before processing
|
||||
- Sanitize user-provided image data
|
||||
- Implement rate limiting for public-facing APIs
|
||||
- Never expose SDK credentials in client-side code
|
||||
|
||||
## Common Use Cases
|
||||
|
||||
1. **Product Analysis**: Analyze product images for e-commerce applications
|
||||
2. **Document Understanding**: Extract information from receipts, invoices, forms
|
||||
3. **Medical Imaging**: Assist in preliminary analysis (with appropriate disclaimers)
|
||||
4. **Quality Control**: Detect defects or anomalies in manufacturing
|
||||
5. **Content Moderation**: Analyze images for policy compliance
|
||||
6. **Accessibility**: Generate alt text for images automatically
|
||||
7. **Visual Search**: Understand and categorize images for search functionality
|
||||
|
||||
## Integration Examples
|
||||
|
||||
### Express.js API Endpoint
|
||||
|
||||
```javascript
|
||||
import express from 'express';
|
||||
import ZAI from 'z-ai-web-dev-sdk';
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
|
||||
let zaiInstance;
|
||||
|
||||
// Initialize SDK once
|
||||
async function initZAI() {
|
||||
zaiInstance = await ZAI.create();
|
||||
}
|
||||
|
||||
app.post('/api/analyze-image', async (req, res) => {
|
||||
try {
|
||||
const { imageUrl, question } = req.body;
|
||||
|
||||
if (!imageUrl || !question) {
|
||||
return res.status(400).json({
|
||||
error: 'imageUrl and question are required'
|
||||
});
|
||||
}
|
||||
|
||||
const response = await zaiInstance.chat.completions.createVision({
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: [
|
||||
{ type: 'text', text: question },
|
||||
{ type: 'image_url', image_url: { url: imageUrl } }
|
||||
]
|
||||
}
|
||||
],
|
||||
thinking: { type: 'disabled' }
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
analysis: response.choices[0]?.message?.content
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
initZAI().then(() => {
|
||||
app.listen(3000, () => {
|
||||
console.log('Vision chat API running on port 3000');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Issue**: "SDK must be used in backend"
|
||||
- **Solution**: Ensure z-ai-web-dev-sdk is only imported and used in server-side code
|
||||
|
||||
**Issue**: Image not loading or being analyzed
|
||||
- **Solution**: Verify the image URL is accessible and returns a valid image format
|
||||
|
||||
**Issue**: Poor analysis quality
|
||||
- **Solution**: Provide more specific prompts and ensure image quality is sufficient
|
||||
|
||||
**Issue**: Slow response times
|
||||
- **Solution**: Optimize image size and consider caching frequently analyzed images
|
||||
|
||||
## Remember
|
||||
|
||||
- Always use z-ai-web-dev-sdk in backend code only
|
||||
- The SDK is already installed - import as shown in examples
|
||||
- Structure prompts clearly for best results
|
||||
- Handle errors gracefully in production applications
|
||||
- Consider user privacy when processing images
|
||||
57
skills/VLM/scripts/vlm.ts
Executable file
57
skills/VLM/scripts/vlm.ts
Executable file
@@ -0,0 +1,57 @@
|
||||
import ZAI, { VisionMessage } from 'z-ai-web-dev-sdk';
|
||||
|
||||
async function main(imageUrl: string, prompt: string) {
|
||||
try {
|
||||
const zai = await ZAI.create();
|
||||
|
||||
const messages: VisionMessage[] = [
|
||||
{
|
||||
role: 'assistant',
|
||||
content: [
|
||||
{ type: 'text', text: 'Output only text, no markdown.' }
|
||||
]
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: [
|
||||
{ type: 'text', text: prompt },
|
||||
{ type: 'image_url', image_url: { url: imageUrl } }
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
// const messages: VisionMessage[] = [
|
||||
// {
|
||||
// role: 'user',
|
||||
// content: [
|
||||
// { type: 'text', text: prompt },
|
||||
// { type: 'video_url', video_url: { url: imageUrl } }
|
||||
// ]
|
||||
// }
|
||||
// ];
|
||||
|
||||
// const messages: VisionMessage[] = [
|
||||
// {
|
||||
// role: 'user',
|
||||
// content: [
|
||||
// { type: 'text', text: prompt },
|
||||
// { type: 'file_url', file_url: { url: imageUrl } }
|
||||
// ]
|
||||
// }
|
||||
// ];
|
||||
|
||||
const response = await zai.chat.completions.createVision({
|
||||
model: 'glm-4.6v',
|
||||
messages,
|
||||
thinking: { type: 'disabled' }
|
||||
});
|
||||
|
||||
const reply = response.choices?.[0]?.message?.content;
|
||||
console.log('Vision model reply:');
|
||||
console.log(reply ?? JSON.stringify(response, null, 2));
|
||||
} catch (err: any) {
|
||||
console.error('Vision chat failed:', err?.message || err);
|
||||
}
|
||||
}
|
||||
|
||||
main("https://cdn.bigmodel.cn/static/logo/register.png", "Please describe this image.");
|
||||
85
skills/docx/CHANGELOG.md
Executable file
85
skills/docx/CHANGELOG.md
Executable file
@@ -0,0 +1,85 @@
|
||||
# Changelog
|
||||
|
||||
## [Added Comment Feature - python-docx Method] - 2026-01-29
|
||||
|
||||
### Added
|
||||
- **批注功能 (Comment Feature)**: 使用python-docx的简单可靠方案
|
||||
- **推荐方法**: `scripts/add_comment_simple.py` - 使用python-docx直接操作.docx文件
|
||||
- **完整示例**: `scripts/examples/add_comments_pythondocx.py` - 展示各种使用场景
|
||||
- SKILL.md: 更新为推荐python-docx方法
|
||||
- ooxml.md: 保留OOXML方法作为高级选项
|
||||
- COMMENTS_UPDATE.md: 详细的功能更新说明
|
||||
|
||||
### Features
|
||||
- ✅ 简单易用:无需解压/打包文档
|
||||
- ✅ 批注人自动设置为"Z.ai"
|
||||
- ✅ 经过实际验证:在Word中正常显示
|
||||
- ✅ 支持多种定位方式:文本搜索、段落索引、条件判断等
|
||||
- ✅ 代码简洁:比OOXML方法简单得多
|
||||
|
||||
### Method Comparison
|
||||
|
||||
**Recommended: python-docx**
|
||||
```python
|
||||
from docx import Document
|
||||
doc = Document('input.docx')
|
||||
doc.add_comment(runs=[para.runs[0]], text="批注", author="Z.ai")
|
||||
doc.save('output.docx')
|
||||
```
|
||||
|
||||
**Alternative: OOXML (Advanced)**
|
||||
```python
|
||||
from scripts.document import Document
|
||||
doc = Document('unpacked', author="Z.ai")
|
||||
para = doc["word/document.xml"].get_node(tag="w:p", contains="text")
|
||||
doc.add_comment(start=para, end=para, text="批注")
|
||||
doc.save()
|
||||
```
|
||||
|
||||
### Usage Examples
|
||||
|
||||
#### 推荐方法(python-docx)
|
||||
```bash
|
||||
# 安装依赖
|
||||
pip install python-docx
|
||||
|
||||
# 使用简单脚本
|
||||
python scripts/add_comment_simple.py input.docx output.docx
|
||||
|
||||
# 使用完整示例
|
||||
python scripts/examples/add_comments_pythondocx.py document.docx reviewed.docx
|
||||
```
|
||||
|
||||
#### 高级方法(OOXML)
|
||||
```bash
|
||||
# 解压、处理、打包
|
||||
python ooxml/scripts/unpack.py document.docx unpacked
|
||||
python scripts/add_comment.py unpacked 10 "批注内容"
|
||||
python ooxml/scripts/pack.py unpacked output.docx
|
||||
```
|
||||
|
||||
### Testing
|
||||
- ✅ python-docx方法经过实际验证
|
||||
- ✅ 批注在Microsoft Word中正常显示
|
||||
- ✅ 作者正确显示为"Z.ai"
|
||||
- ✅ 支持各种定位方式
|
||||
- ✅ 代码简洁可靠
|
||||
|
||||
### Documentation
|
||||
- SKILL.md: 推荐python-docx方法,保留OOXML作为高级选项
|
||||
- COMMENTS_UPDATE.md: 详细说明两种方法的区别
|
||||
- 新增python-docx示例脚本
|
||||
- 保留OOXML示例供高级用户使用
|
||||
|
||||
### Why python-docx is Recommended
|
||||
1. **简单**: 无需解压/打包文档
|
||||
2. **可靠**: 经过实际验证,在Word中正常工作
|
||||
3. **直接**: 直接操作.docx文件,一步到位
|
||||
4. **维护性**: 代码简洁,易于理解和修改
|
||||
5. **兼容性**: 使用标准库,兼容性好
|
||||
|
||||
OOXML方法适合:
|
||||
- 需要低级XML控制
|
||||
- 需要同时处理tracked changes
|
||||
- 需要批注回复等复杂功能
|
||||
- 已经在使用解压文档的工作流
|
||||
30
skills/docx/LICENSE.txt
Executable file
30
skills/docx/LICENSE.txt
Executable file
@@ -0,0 +1,30 @@
|
||||
© 2025 Anthropic, PBC. All rights reserved.
|
||||
|
||||
LICENSE: Use of these materials (including all code, prompts, assets, files,
|
||||
and other components of this Skill) is governed by your agreement with
|
||||
Anthropic regarding use of Anthropic's services. If no separate agreement
|
||||
exists, use is governed by Anthropic's Consumer Terms of Service or
|
||||
Commercial Terms of Service, as applicable:
|
||||
https://www.anthropic.com/legal/consumer-terms
|
||||
https://www.anthropic.com/legal/commercial-terms
|
||||
Your applicable agreement is referred to as the "Agreement." "Services" are
|
||||
as defined in the Agreement.
|
||||
|
||||
ADDITIONAL RESTRICTIONS: Notwithstanding anything in the Agreement to the
|
||||
contrary, users may not:
|
||||
|
||||
- Extract these materials from the Services or retain copies of these
|
||||
materials outside the Services
|
||||
- Reproduce or copy these materials, except for temporary copies created
|
||||
automatically during authorized use of the Services
|
||||
- Create derivative works based on these materials
|
||||
- Distribute, sublicense, or transfer these materials to any third party
|
||||
- Make, offer to sell, sell, or import any inventions embodied in these
|
||||
materials
|
||||
- Reverse engineer, decompile, or disassemble these materials
|
||||
|
||||
The receipt, viewing, or possession of these materials does not convey or
|
||||
imply any license or right beyond those expressly granted above.
|
||||
|
||||
Anthropic retains all right, title, and interest in these materials,
|
||||
including all copyrights, patents, and other intellectual property rights.
|
||||
455
skills/docx/SKILL.md
Executable file
455
skills/docx/SKILL.md
Executable file
@@ -0,0 +1,455 @@
|
||||
---
|
||||
name: docx
|
||||
description: "Comprehensive document creation, editing, and analysis with support for tracked changes, comments, formatting preservation, and text extraction. When GLM needs to work with professional documents (.docx files) for: (1) Creating new documents, (2) Modifying or editing content, (3) Working with tracked changes, (4) Adding comments, or any other document tasks"
|
||||
license: Proprietary. LICENSE.txt has complete terms
|
||||
---
|
||||
|
||||
# DOCX creation, editing, and analysis
|
||||
|
||||
## Overview
|
||||
|
||||
A user may ask you to create, edit, or analyze the contents of a .docx file. A .docx file is essentially a ZIP archive containing XML files and other resources that you can read or edit. You have different tools and workflows available for different tasks.
|
||||
|
||||
# Design requiremnet
|
||||
|
||||
Deliver studio-quality Word documents with deep thought on content, functionality, and styling. Users often don't explicitly request advanced features (covers, TOC, backgrounds, back covers, footnotes, charts)—deeply understand needs and proactively extend. The document must have 1.3x line spacing and have charts centered horizontally.
|
||||
## Available color(choose one)
|
||||
- "Ink & Zen" Color Palette (Wabi-Sabi Style)
|
||||
The design uses a grayscale "Ink" palette to differentiate from standard business blue/morandi styles.
|
||||
Primary (Titles):#0B1220
|
||||
Body Text:#0F172A
|
||||
Secondary (Subtitles):#2B2B2B
|
||||
Accent (UI / Decor):#9AA6B2
|
||||
Table Header / Subtle Background:#F1F5F9
|
||||
|
||||
- Wilderness Oasis": Sage & Deep Forest
|
||||
Primary (Titles): #1A1F16 (Deep Forest Ink)
|
||||
Body Text: #2D3329 (Dark Moss Gray)
|
||||
Secondary (Subtitles): #4A5548 (Neutral Olive)
|
||||
Accent (UI/Decor): #94A3B8 (Steady Silver)
|
||||
Table/Background: #F8FAF7 (Ultra-Pale Mint White)
|
||||
|
||||
- "Terra Cotta Afterglow": Warm Clay & Greige
|
||||
Commonly utilized by top-tier consulting firms and architectural studios, this scheme warms up the gray scale to create a tactile sensation similar to premium cashmere.
|
||||
Primary (Titles): #26211F (Deep Charcoal Espresso)
|
||||
Body Text: #3D3735 (Dark Umber Gray)
|
||||
Secondary (Subtitles): #6B6361 (Warm Greige)
|
||||
Accent (UI/Decor): #C19A6B (Terra Cotta Gold / Muted Ochre)
|
||||
Table/Background: #FDFCFB (Off-White / Paper Texture)
|
||||
|
||||
- "Midnight Code": High-Contrast Slate & Silver
|
||||
Ideal for cutting-edge technology, AI ventures, or digital transformation projects. This palette carries a slight "electric" undertone that provides superior visual penetration.
|
||||
Primary (Titles): #020617 (Midnight Black)
|
||||
Body Text: #1E293B (Deep Slate Blue)
|
||||
Secondary (Subtitles): #64748B (Cool Blue-Gray)
|
||||
Accent (UI/Decor): #94A3B8 (Steady Silver)
|
||||
Table/Background: #F8FAFC (Glacial Blue-White)
|
||||
|
||||
### Chinese plot PNG method**
|
||||
If using Python to generate PNGs containing Chinese characters, note that Matplotlib defaults to the DejaVu Sans font which lacks Chinese support; since the environment already has the SimHei font installed, you should set it as the default by configuring:
|
||||
|
||||
matplotlib.font_manager.fontManager.addfont('/usr/share/fonts/truetype/chinese/SimHei.ttf')
|
||||
plt.rcParams['font.sans-serif'] = ['SimHei']
|
||||
plt.rcParams['axes.unicode_minus'] = False
|
||||
|
||||
|
||||
|
||||
|
||||
## Specialized Element Styling
|
||||
- Table Borders: Use a "Single" line style with a size of 12 and the Primary Ink color. Internal vertical borders should be set to Nil (invisible) to create a clean, modern horizontal-only look.
|
||||
- **CRITICAL: Table Cell Margins** - ALL tables MUST set `margins` property at the Table level to prevent text from touching borders. This is mandatory for professional document quality.
|
||||
|
||||
### Alignment and Typography
|
||||
CJK body: justify + 2-char indent. English: left. Table numbers: right. Headings: no indent.
|
||||
For both languages, Must use a line spacing of 1.3x (250 twips). Do not use single line spacing !!!
|
||||
|
||||
### CRITICAL: Chinese Quotes in JavaScript/TypeScript Code
|
||||
**MANDATORY**: When writing JavaScript/TypeScript code for docx-js, ALL Chinese quotation marks (""", ''') inside strings MUST be escaped as Unicode escape sequences:
|
||||
- Left double quote "\u201c" (")
|
||||
- Right double quote "\u201d" (")
|
||||
- Left single quote "\u2018" (')
|
||||
- Right single quote "\u2019" (')
|
||||
|
||||
**Example - INCORRECT (will cause syntax error):**
|
||||
```javascript
|
||||
new TextRun({
|
||||
text: "他说"你好"" // ERROR: Chinese quotes break JS syntax
|
||||
})
|
||||
```
|
||||
|
||||
**Example - CORRECT:**
|
||||
```javascript
|
||||
new TextRun({
|
||||
text: "他说\u201c你好\u201d" // Correct: escaped Unicode
|
||||
})
|
||||
```
|
||||
|
||||
**Alternative - Use template literals:**
|
||||
```javascript
|
||||
new TextRun({
|
||||
text: `他说"你好"` // Also works: template literals allow Chinese quotes
|
||||
})
|
||||
```
|
||||
|
||||
## Workflow Decision Tree
|
||||
|
||||
### Reading/Analyzing Content
|
||||
Use "Text extraction" or "Raw XML access" sections below.
|
||||
|
||||
### Creating New Document
|
||||
Use "Creating a new Word document" workflow.
|
||||
|
||||
### Editing Existing Document
|
||||
- **Your own document + simple changes**
|
||||
Use "Basic OOXML editing" workflow
|
||||
|
||||
- **Someone else's document**
|
||||
Use **"Redlining workflow"** (recommended default)
|
||||
|
||||
- **Legal, academic, business, or government docs**
|
||||
Use **"Redlining workflow"** (required)
|
||||
|
||||
## Reading and analyzing content
|
||||
|
||||
**Note**: For .doc (legacy format), first convert with `libreoffice --convert-to docx file.doc`.
|
||||
|
||||
### Text extraction
|
||||
If you just need to read the text contents of a document, you should convert the document to markdown using pandoc. Pandoc provides excellent support for preserving document structure and can show tracked changes:
|
||||
|
||||
```bash
|
||||
# Convert document to markdown with tracked changes
|
||||
pandoc --track-changes=all path-to-file.docx -o output.md
|
||||
# Options: --track-changes=accept/reject/all
|
||||
```
|
||||
|
||||
### Raw XML access
|
||||
You need raw XML access for: comments, complex formatting, document structure, embedded media, and metadata. For any of these features, you'll need to unpack a document and read its raw XML contents.
|
||||
|
||||
#### Unpacking a file
|
||||
`python ooxml/scripts/unpack.py <office_file> <output_directory>`
|
||||
|
||||
#### Key file structures
|
||||
* `word/document.xml` - Main document contents
|
||||
* `word/comments.xml` - Comments referenced in document.xml
|
||||
* `word/media/` - Embedded images and media files
|
||||
* Tracked changes use `<w:ins>` (insertions) and `<w:del>` (deletions) tags
|
||||
|
||||
## Creating a new Word document
|
||||
|
||||
When creating a new Word document from scratch, use **docx-js**, but use bun instead of node to implement it. which allows you to create Word documents using JavaScript/TypeScript.
|
||||
|
||||
### Workflow
|
||||
1. **MANDATORY - READ ENTIRE FILE**: Read [`docx-js.md`](docx-js.md) (~560 lines) completely from start to finish. **NEVER set any range limits when reading this file.** Read the full file content for detailed syntax, critical formatting rules, and best practices before proceeding with document creation.
|
||||
2. Create a JavaScript/TypeScript file using Document, Paragraph, TextRun components (You can assume all dependencies are installed, but if not, refer to the dependencies section below)
|
||||
3. Export as .docx using Packer.toBuffer()
|
||||
|
||||
### TOC (Table of Contents)
|
||||
**If the document has more than three sections, generate a table of contents.**
|
||||
|
||||
**Implementation**: Use docx-js `TableOfContents` component to create a live TOC that auto-populates from document headings.
|
||||
|
||||
**CRITICAL**: For TOC to work correctly:
|
||||
- All document headings MUST use `HeadingLevel` (e.g., `HeadingLevel.HEADING_1`)
|
||||
- Do NOT add custom styles to heading paragraphs
|
||||
- Place TOC before the actual heading content so it can scan them
|
||||
|
||||
**Hint requirement**: A hint paragraph MUST be added immediately after the TOC component with these specifications:
|
||||
- **Position**: Immediately after the TOC component
|
||||
- **Alignment**: Center-aligned
|
||||
- **Color**: Gray (e.g., "999999")
|
||||
- **Font size**: 18 (9pt)
|
||||
- **Language**: Matches user conversation language
|
||||
- **Text content**: Inform the user to right-click the TOC and select "Update Field" to show correct page numbers
|
||||
|
||||
### TOC Placeholders (Required Post-Processing)
|
||||
|
||||
**REQUIRED**: After generating the DOCX file, you MUST add placeholder TOC entries that appear on first open (before the user updates the TOC). This prevents showing an empty TOC initially.
|
||||
|
||||
**Implementation**: Always run the `add_toc_placeholders.py` script after generating the DOCX file:
|
||||
|
||||
```bash
|
||||
python skills/docx/scripts/add_toc_placeholders.py document.docx \
|
||||
--entries '[{"level":1,"text":"Chapter 1 Overview","page":"1"},{"level":2,"text":"Section 1.1 Details","page":"1"}]'
|
||||
```
|
||||
|
||||
**Note**: The script supports up to 3 TOC levels for placeholder entries.
|
||||
|
||||
**Entry format**:
|
||||
- `level`: Heading level (1, 2, or 3)
|
||||
- `text`: The heading text
|
||||
- `page`: Estimated page number (will be corrected when TOC is updated)
|
||||
|
||||
**Auto-generating entries**:
|
||||
You can extract the actual headings from the document structure to generate accurate entries. Match the heading text and hierarchy from your document content.
|
||||
|
||||
**Benefits**:
|
||||
- Users see TOC content immediately on first open
|
||||
- Placeholders are automatically replaced when user updates the TOC
|
||||
- Improves perceived document quality and user experience
|
||||
|
||||
### Document Formatting Rules
|
||||
|
||||
**Page Break Restrictions**
|
||||
Page breaks are ONLY allowed in these specific locations:
|
||||
- Between cover page and table of contents (if TOC exists)
|
||||
- Between cover page and main content (if NO TOC exists)
|
||||
- Between table of contents and main content (if TOC exists)
|
||||
|
||||
**All content after the table of contents must flow continuously WITHOUT page breaks.**
|
||||
|
||||
**Text and Paragraph Rules**
|
||||
- Complete sentences before starting a new line — do not break sentences across lines
|
||||
- Use single, consistent style for each complete sentence
|
||||
- Only start a new paragraph when the current paragraph is logically complete
|
||||
|
||||
**List and Bullet Point Formatting**
|
||||
- Use left-aligned formatting (NOT justified alignment)
|
||||
- Insert a line break after each list item
|
||||
- Never place multiple items on the same line (justification stretches text)
|
||||
|
||||
## Editing an existing Word document
|
||||
|
||||
**Note**: For .doc (legacy format), first convert with `libreoffice --convert-to docx file.doc`.
|
||||
|
||||
When editing an existing Word document, use the **Document library** (a Python library for OOXML manipulation). The library automatically handles infrastructure setup and provides methods for document manipulation. For complex scenarios, you can access the underlying DOM directly through the library.
|
||||
|
||||
### Workflow
|
||||
1. **MANDATORY - READ ENTIRE FILE**: Read [`ooxml.md`](ooxml.md) (~600 lines) completely from start to finish. **NEVER set any range limits when reading this file.** Read the full file content for the Document library API and XML patterns for directly editing document files.
|
||||
2. Unpack the document: `python ooxml/scripts/unpack.py <office_file> <output_directory>`
|
||||
3. Create and run a Python script using the Document library (see "Document Library" section in ooxml.md)
|
||||
4. Pack the final document: `python ooxml/scripts/pack.py <input_directory> <office_file>`
|
||||
|
||||
The Document library provides both high-level methods for common operations and direct DOM access for complex scenarios.
|
||||
|
||||
## Adding Comments (批注)
|
||||
|
||||
Comments (批注) allow you to add annotations to documents without modifying the actual content. This is useful for review feedback, explanations, or questions about specific parts of a document.
|
||||
|
||||
### Recommended Method: Using python-docx (简单推荐)
|
||||
|
||||
The simplest and most reliable way to add comments is using the `python-docx` library:
|
||||
|
||||
```python
|
||||
from docx import Document
|
||||
|
||||
# Open the document
|
||||
doc = Document('input.docx')
|
||||
|
||||
# Find paragraphs and add comments
|
||||
for para in doc.paragraphs:
|
||||
if "关键词" in para.text: # Find paragraphs containing specific text
|
||||
doc.add_comment(
|
||||
runs=[para.runs[0]], # Specify the text to comment on
|
||||
text="批注内容",
|
||||
author="Z.ai" # Set comment author as Z.ai
|
||||
)
|
||||
|
||||
# Save the document
|
||||
doc.save('output.docx')
|
||||
```
|
||||
|
||||
**Key points:**
|
||||
- Install: `pip install python-docx` or `bun add python-docx`
|
||||
- Works directly on .docx files (no need to unpack/pack)
|
||||
- Simple API, reliable results
|
||||
- Comments appear in Word's comment pane with Z.ai as author
|
||||
|
||||
**Common patterns:**
|
||||
|
||||
```python
|
||||
from docx import Document
|
||||
|
||||
doc = Document('document.docx')
|
||||
|
||||
# Add comment to first paragraph
|
||||
if doc.paragraphs:
|
||||
first_para = doc.paragraphs[0]
|
||||
doc.add_comment(
|
||||
runs=[first_para.runs[0]] if first_para.runs else [],
|
||||
text="Review this introduction",
|
||||
author="Z.ai"
|
||||
)
|
||||
|
||||
# Add comment to specific paragraph by index
|
||||
target_para = doc.paragraphs[5] # 6th paragraph
|
||||
doc.add_comment(
|
||||
runs=[target_para.runs[0]],
|
||||
text="This section needs clarification",
|
||||
author="Z.ai"
|
||||
)
|
||||
|
||||
# Add comments based on text search
|
||||
for para in doc.paragraphs:
|
||||
if "important" in para.text.lower():
|
||||
doc.add_comment(
|
||||
runs=[para.runs[0]],
|
||||
text="Flagged for review",
|
||||
author="Z.ai"
|
||||
)
|
||||
|
||||
doc.save('output.docx')
|
||||
```
|
||||
|
||||
### Alternative Method: Using OOXML (Advanced)
|
||||
|
||||
For complex scenarios requiring low-level XML manipulation, you can use the OOXML workflow. This method is more complex but provides finer control.
|
||||
|
||||
**Note:** This method requires unpacking/packing documents and may encounter validation issues. Use python-docx unless you specifically need low-level XML control.
|
||||
|
||||
#### OOXML Workflow
|
||||
|
||||
1. **Unpack the document**: `python ooxml/scripts/unpack.py <file.docx> <output_dir>`
|
||||
|
||||
2. **Create and run a Python script**:
|
||||
|
||||
```python
|
||||
from scripts.document import Document
|
||||
|
||||
# Initialize with Z.ai as the author
|
||||
doc = Document('unpacked', author="Z.ai", initials="Z")
|
||||
|
||||
# Add comment on a paragraph
|
||||
para = doc["word/document.xml"].get_node(tag="w:p", contains="paragraph text")
|
||||
doc.add_comment(start=para, end=para, text="This needs clarification")
|
||||
|
||||
# Save changes
|
||||
doc.save()
|
||||
```
|
||||
|
||||
3. **Pack the document**: `python ooxml/scripts/pack.py <unpacked_dir> <output.docx>`
|
||||
|
||||
**When to use OOXML method:**
|
||||
- You need to work with tracked changes simultaneously
|
||||
- You need fine-grained control over XML structure
|
||||
- You're already working with unpacked documents
|
||||
- You need to manipulate comments in complex ways
|
||||
|
||||
**When to use python-docx method (recommended):**
|
||||
- Adding comments is your primary task
|
||||
- You want simple, reliable code
|
||||
- You're working with complete .docx files
|
||||
- You don't need low-level XML access
|
||||
|
||||
## Redlining workflow for document review
|
||||
|
||||
This workflow allows you to plan comprehensive tracked changes using markdown before implementing them in OOXML. **CRITICAL**: For complete tracked changes, you must implement ALL changes systematically.
|
||||
|
||||
**Batching Strategy**: Group related changes into batches of 3-10 changes. This makes debugging manageable while maintaining efficiency. Test each batch before moving to the next.
|
||||
|
||||
**Principle: Minimal, Precise Edits**
|
||||
When implementing tracked changes, only mark text that actually changes. Repeating unchanged text makes edits harder to review and appears unprofessional. Break replacements into: [unchanged text] + [deletion] + [insertion] + [unchanged text]. Preserve the original run's RSID for unchanged text by extracting the `<w:r>` element from the original and reusing it.
|
||||
|
||||
Example - Changing "30 days" to "60 days" in a sentence:
|
||||
```python
|
||||
# BAD - Replaces entire sentence
|
||||
'<w:del><w:r><w:delText>The term is 30 days.</w:delText></w:r></w:del><w:ins><w:r><w:t>The term is 60 days.</w:t></w:r></w:ins>'
|
||||
|
||||
# GOOD - Only marks what changed, preserves original <w:r> for unchanged text
|
||||
'<w:r w:rsidR="00AB12CD"><w:t>The term is </w:t></w:r><w:del><w:r><w:delText>30</w:delText></w:r></w:del><w:ins><w:r><w:t>60</w:t></w:r></w:ins><w:r w:rsidR="00AB12CD"><w:t> days.</w:t></w:r>'
|
||||
```
|
||||
|
||||
### Tracked changes workflow
|
||||
|
||||
1. **Get markdown representation**: Convert document to markdown with tracked changes preserved:
|
||||
```bash
|
||||
pandoc --track-changes=all path-to-file.docx -o current.md
|
||||
```
|
||||
|
||||
2. **Identify and group changes**: Review the document and identify ALL changes needed, organizing them into logical batches:
|
||||
|
||||
**Location methods** (for finding changes in XML):
|
||||
- Section/heading numbers (e.g., "Section 3.2", "Article IV")
|
||||
- Paragraph identifiers if numbered
|
||||
- Grep patterns with unique surrounding text
|
||||
- Document structure (e.g., "first paragraph", "signature block")
|
||||
- **DO NOT use markdown line numbers** - they don't map to XML structure
|
||||
|
||||
**Batch organization** (group 3-10 related changes per batch):
|
||||
- By section: "Batch 1: Section 2 amendments", "Batch 2: Section 5 updates"
|
||||
- By type: "Batch 1: Date corrections", "Batch 2: Party name changes"
|
||||
- By complexity: Start with simple text replacements, then tackle complex structural changes
|
||||
- Sequential: "Batch 1: Pages 1-3", "Batch 2: Pages 4-6"
|
||||
|
||||
3. **Read documentation and unpack**:
|
||||
- **MANDATORY - READ ENTIRE FILE**: Read [`ooxml.md`](ooxml.md) (~600 lines) completely from start to finish. **NEVER set any range limits when reading this file.** Pay special attention to the "Document Library" and "Tracked Change Patterns" sections.
|
||||
- **Unpack the document**: `python ooxml/scripts/unpack.py <file.docx> <dir>`
|
||||
- **Note the suggested RSID**: The unpack script will suggest an RSID to use for your tracked changes. Copy this RSID for use in step 4b.
|
||||
|
||||
4. **Implement changes in batches**: Group changes logically (by section, by type, or by proximity) and implement them together in a single script. This approach:
|
||||
- Makes debugging easier (smaller batch = easier to isolate errors)
|
||||
- Allows incremental progress
|
||||
- Maintains efficiency (batch size of 3-10 changes works well)
|
||||
|
||||
**Suggested batch groupings:**
|
||||
- By document section (e.g., "Section 3 changes", "Definitions", "Termination clause")
|
||||
- By change type (e.g., "Date changes", "Party name updates", "Legal term replacements")
|
||||
- By proximity (e.g., "Changes on pages 1-3", "Changes in first half of document")
|
||||
|
||||
For each batch of related changes:
|
||||
|
||||
**a. Map text to XML**: Grep for text in `word/document.xml` to verify how text is split across `<w:r>` elements.
|
||||
|
||||
**b. Create and run script**: Use `get_node` to find nodes, implement changes, then `doc.save()`. See **"Document Library"** section in ooxml.md for patterns.
|
||||
|
||||
**Note**: Always grep `word/document.xml` immediately before writing a script to get current line numbers and verify text content. Line numbers change after each script run.
|
||||
|
||||
5. **Pack the document**: After all batches are complete, convert the unpacked directory back to .docx:
|
||||
```bash
|
||||
python ooxml/scripts/pack.py unpacked reviewed-document.docx
|
||||
```
|
||||
|
||||
6. **Final verification**: Do a comprehensive check of the complete document:
|
||||
- Convert final document to markdown:
|
||||
```bash
|
||||
pandoc --track-changes=all reviewed-document.docx -o verification.md
|
||||
```
|
||||
- Verify ALL changes were applied correctly:
|
||||
```bash
|
||||
grep "original phrase" verification.md # Should NOT find it
|
||||
grep "replacement phrase" verification.md # Should find it
|
||||
```
|
||||
- Check that no unintended changes were introduced
|
||||
|
||||
|
||||
## Converting Documents to Images
|
||||
|
||||
To visually analyze Word documents, convert them to images using a two-step process:
|
||||
|
||||
1. **Convert DOCX to PDF**:
|
||||
```bash
|
||||
soffice --headless --convert-to pdf document.docx
|
||||
```
|
||||
|
||||
2. **Convert PDF pages to JPEG images**:
|
||||
```bash
|
||||
pdftoppm -jpeg -r 150 document.pdf page
|
||||
```
|
||||
This creates files like `page-1.jpg`, `page-2.jpg`, etc.
|
||||
|
||||
Options:
|
||||
- `-r 150`: Sets resolution to 150 DPI (adjust for quality/size balance)
|
||||
- `-jpeg`: Output JPEG format (use `-png` for PNG if preferred)
|
||||
- `-f N`: First page to convert (e.g., `-f 2` starts from page 2)
|
||||
- `-l N`: Last page to convert (e.g., `-l 5` stops at page 5)
|
||||
- `page`: Prefix for output files
|
||||
|
||||
Example for specific range:
|
||||
```bash
|
||||
pdftoppm -jpeg -r 150 -f 2 -l 5 document.pdf page # Converts only pages 2-5
|
||||
```
|
||||
|
||||
## Code Style Guidelines
|
||||
**IMPORTANT**: When generating code for DOCX operations:
|
||||
- Write concise code
|
||||
- Avoid verbose variable names and redundant operations
|
||||
- Avoid unnecessary print statements
|
||||
|
||||
## Dependencies
|
||||
|
||||
Required dependencies (install if not available):
|
||||
|
||||
- **pandoc**: `sudo apt-get install pandoc` (for text extraction)
|
||||
- **docx**: `bun add docx` (for creating new documents)
|
||||
- **LibreOffice**: `sudo apt-get install libreoffice` (for PDF conversion)
|
||||
- **Poppler**: `sudo apt-get install poppler-utils` (for pdftoppm to convert PDF to images)
|
||||
- **defusedxml**: `pip install defusedxml` (for secure XML parsing)
|
||||
681
skills/docx/docx-js.md
Executable file
681
skills/docx/docx-js.md
Executable file
@@ -0,0 +1,681 @@
|
||||
# DOCX Library Tutorial
|
||||
|
||||
Generate .docx files with JavaScript/TypeScript.
|
||||
|
||||
**Important: Read this entire document before starting.** Critical formatting rules and common pitfalls are covered throughout - skipping sections may result in corrupted files or rendering issues.
|
||||
|
||||
## Setup
|
||||
Assumes docx is already installed globally
|
||||
If not installed: first try `bun add docx`, then `npm install -g docx`
|
||||
```javascript
|
||||
const { Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell, ImageRun, Media,
|
||||
Header, Footer, AlignmentType, PageOrientation, LevelFormat, ExternalHyperlink,
|
||||
InternalHyperlink, TableOfContents, HeadingLevel, BorderStyle, WidthType, TabStopType,
|
||||
TabStopPosition, UnderlineType, ShadingType, VerticalAlign, SymbolRun, PageNumber,
|
||||
FootnoteReferenceRun, Footnote, PageBreak } = require('docx');
|
||||
|
||||
// Create & Save
|
||||
const doc = new Document({ sections: [{ children: [/* content */] }] });
|
||||
Packer.toBuffer(doc).then(buffer => fs.writeFileSync("doc.docx", buffer)); // Node.js
|
||||
Packer.toBlob(doc).then(blob => { /* download logic */ }); // Browser
|
||||
```
|
||||
|
||||
## Delivery Standard
|
||||
|
||||
**Generic styling and mediocre aesthetics = mediocre delivery.**
|
||||
|
||||
Deliver studio-quality Word documents with deep thought on content, functionality, and styling. Users often don't explicitly request advanced features (covers, TOC, backgrounds, back covers, footnotes, charts)—deeply understand needs and proactively extend.
|
||||
|
||||
The following formatting standards are to be strictly applied without exception:
|
||||
|
||||
- Line Spacing: The entire document must use 1.3x line spacing.
|
||||
- Chart/Figure Placement: All charts, graphs, and figures must be explicitly centered horizontally on the page.
|
||||
|
||||
```javascript
|
||||
new Table({
|
||||
alignment: AlignmentType.CENTER,
|
||||
rows: [
|
||||
new TableRow({
|
||||
children: [
|
||||
new TableCell({
|
||||
children: [
|
||||
new Paragraph({
|
||||
text: "centered text",
|
||||
alignment: AlignmentType.CENTER,
|
||||
}),
|
||||
],
|
||||
verticalAlign: VerticalAlign.CENTER,
|
||||
shading: { fill: colors.tableBg },
|
||||
borders: cellBorders,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
- The text in charts must have left/right/up/bottom margin.
|
||||
- Image Handling:Preserve aspect ratio**: Never adjust image aspect ratio. Must insert according to the original ratio.
|
||||
- Do not use background shading to all table section headers.
|
||||
|
||||
Compliance with these specifications is mandatory.
|
||||
|
||||
## Language Consistency
|
||||
|
||||
**Document language = User conversation language** (including filename, body text, headings, headers, TOC hints, chart labels, and all other text).
|
||||
|
||||
## Headers and Footers - REQUIRED BY DEFAULT
|
||||
|
||||
Most documents **MUST** include headers and footers. The specific style (alignment, format, content) should match the document's overall design.
|
||||
|
||||
- **Header**: Typically document title, company name, or chapter name
|
||||
- **Footer**: Typically page numbers (format flexible: "X / Y", "Page X", "— X —", etc.)
|
||||
- **Cover/Back cover**: Use `TitlePage` setting to hide header/footer on first page
|
||||
|
||||
## Fonts
|
||||
If the user do not require specific fonts, you must follow the fonts rule belowing:
|
||||
### For Chinese:
|
||||
| Element | Font Family | Font Size (Half-points) | Properties |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| Normal Body | Microsoft YaHei (微软雅黑) | 21 (10.5pt / 五号) | Standard for readability. |
|
||||
| Heading 1 | SimHei (黑体) | 32 (16pt / 三号) | Bold, high impact. |
|
||||
| Heading 2 | SimHei (黑体) | 28 (14pt / 四号) | Bold. |
|
||||
| Caption | Microsoft YaHei | 20 (10pt) | For tables and charts. |
|
||||
|
||||
- Microsoft YaHei, located at /usr/share/fonts/truetype/chinese/msyh.ttf
|
||||
- SimHei, located at /usr/share/fonts/truetype/chinese/SimHei.ttf
|
||||
- Code blocks: SarasaMonoSC, located at /usr/share/fonts/truetype/chinese/SarasaMonoSC-Regular.ttf
|
||||
- Formulas / symbols: DejaVuSans, located at /usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf
|
||||
- For body text and formulas, use Paragraph instead of Preformatted.
|
||||
|
||||
|
||||
### For English
|
||||
| Element | Font Family | Font Size (Half-points) | Properties |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| Normal Body | Calibri | 22 (11pt) | Highly legible; slightly larger than 10.5pt to match visual "weight." |
|
||||
| Heading 1 | Times New Roman | 36 (18pt) | Bold, Serif; provides a clear "Newspaper" style hierarchy. |
|
||||
| Heading 2 | Times New Roman | 28 (14pt) | Bold; classic and professional. |
|
||||
| Caption | Calibri | 18 (9pt) | Clean and compact for metadata and notes. |
|
||||
|
||||
- Times New Roman, located at /usr/share/fonts/truetype/english/Times-New-Roman.ttf
|
||||
- Calibri,located at /usr/share/fonts/truetype/english/calibri-regular.ttf
|
||||
|
||||
## Spacing & Paragraph Alignment
|
||||
Task: Apply the following formatting rules to the provided text for a professional bilingual (Chinese/English) layout.
|
||||
### Paragraph & Indentation:
|
||||
Chinese Body: First-line indent of 2 characters (420 twips).
|
||||
English Body: No first-line indent; use block format (space between paragraphs).
|
||||
Alignment: Justified (Both) for all body text; Centered for Titles and Table Headers.
|
||||
### Line & Paragraph Spacing(keep in mind)
|
||||
Line Spacing: Set to 1.3 (250 twips) lines for both languages.
|
||||
Heading 1: 600 twips before, 300 twips after.
|
||||
### Mixed-Language Kerning:
|
||||
Insert a standard half-width space between Chinese characters and English words/numbers (e.g., "共 20 个 items").
|
||||
### Punctuation:
|
||||
Use full-width punctuation for Chinese text and half-width punctuation for English text.
|
||||
|
||||
## Professional Elements (Critical)
|
||||
|
||||
Produce documents that surpass user expectations by proactively incorporating high-end design elements without being prompted. Quality Benchmark: Visual excellence reflecting the standards of a top-tier designer in 2025.
|
||||
|
||||
**Cover & Visual:**
|
||||
- Double-Sided Branding: All formal documents (proposals, reports, contracts, bids) and creative assets (invitations, greeting cards) must include both a standalone front and back cover.
|
||||
- Internal Accents: Body pages may include subtle background elements to enhance the overall aesthetic depth.
|
||||
|
||||
**Structure:**
|
||||
- Navigation: For any document with three or more sections, include a Table of Contents (TOC) immediately followed by a "refresh hint."
|
||||
|
||||
**Data Presentation:**
|
||||
- Visual Priority: Use professional charts to illustrate trends or comparisons rather than plain text lists.
|
||||
- Table Aesthetics: Apply light gray headers or the "three-line" professional style; strictly avoid the default Word blue.
|
||||
|
||||
**Links & References:**
|
||||
- Interactive Links: All URLs must be formatted as clickable, active hyperlinks.
|
||||
- Cross-Referencing: Number all figures and tables systematically (e.g., "see Figure 1") and use internal cross-references.
|
||||
- Academic/Legal Rigor: For research or data-heavy documents, implement clickable in-text citations paired with accurate footnotes or endnotes.
|
||||
|
||||
### TOC Refresh Hint
|
||||
|
||||
Because Word TOCs utilize field codes, page numbers may become unaligned during generation. You must append the following gray hint text after the TOC to guide the user:
|
||||
Note: This Table of Contents is generated via field codes. To ensure page number accuracy after editing, please right-click the TOC and select "Update Field."
|
||||
|
||||
### Outline Adherence
|
||||
|
||||
- **User provides outline**: Follow strictly, no additions, deletions, or reordering
|
||||
- **No outline provided**: Use standard structure
|
||||
- Academic: Introduction → Literature Review → Methodology → Results → Discussion → Conclusion.
|
||||
- Business: Executive Summary → Analysis → Recommendations.
|
||||
- Technical: Overview → Principles → Implementation → Examples → FAQ.
|
||||
|
||||
### Scene Completeness
|
||||
|
||||
Anticipate the functional requirements of the specific scenario. Examples include, but are not limited to:
|
||||
- **Exam paper** → Include name/class/ID fields, point allocations for every question, and a dedicated grading table.
|
||||
- **Contract** → Provide signature and seal blocks for all parties, date placeholders, contract ID numbers, and an attachment list.
|
||||
- **Meeting minutes** → List attendees and absentees, define action items with assigned owners, and note the next meeting time.
|
||||
|
||||
## Design Philosophy
|
||||
|
||||
### Color Scheme
|
||||
|
||||
**Low saturation tones**, avoid Word default blue and matplotlib default high saturation.
|
||||
|
||||
**Flexibly choose** color schemes based on document scenario:
|
||||
|
||||
| Style | Palette | Suitable Scenarios |
|
||||
|-------|---------|-------------------|
|
||||
| Morandi | Soft muted tones | Arts, editorial, lifestyle |
|
||||
| Earth tones | Brown, olive, natural | Environmental, organic industries |
|
||||
| Nordic | Cool gray, misty blue | Minimalism, technology, software |
|
||||
| Japanese Wabi-sabi | Gray, raw wood, zen | Traditional, contemplative, crafts |
|
||||
| French elegance | Off-white, dusty pink | Luxury, fashion, high-end retail |
|
||||
| Industrial | Charcoal, rust, concrete | Manufacturing, engineering, construction |
|
||||
| Academic | Navy, burgundy, ivory | Research, education, legal |
|
||||
| Ocean mist | Misty blue, sand | Marine, wellness, travel |
|
||||
| Forest moss | Olive, moss green | Nature, sustainability, forestry |
|
||||
| Desert dusk | Ochre, sandy gold | Warmth, regional, historical |
|
||||
|
||||
**Color scheme must be consistent within the same document.**
|
||||
|
||||
### highlighting
|
||||
Use low saturation color schemes for font highlighting.
|
||||
|
||||
### Layout
|
||||
|
||||
White space (margins, paragraph spacing), clear hierarchy (H1 > H2 > body), proper padding (text shouldn't touch borders).
|
||||
|
||||
### Pagination Control
|
||||
|
||||
Word uses flow layout, not fixed pages.
|
||||
|
||||
### Alignment and Typography (keep in mind!!!)
|
||||
CJK body: justify + 2-char indent. English: left. Table numbers: right. Headings: no indent.
|
||||
For both languages, Must use a line spacing of 1.3x (250 twips). Do not use single line spacing !!!
|
||||
|
||||
### Table Formatting(Very inportant)
|
||||
- A caption must be added immediately after the table, keep in mind!
|
||||
- The entire table must be centered horizontally on the page. keep in mind!
|
||||
#### Cell Formatting (Inside the Table)
|
||||
Left/Right Cell Margin: Set to at least 120-200 twips (approximately the width of one character).
|
||||
Up/Down Cell Margin: Set to at least 100 twips
|
||||
Text Alignment(must follow !!!):
|
||||
- Horizontal Alignment: Center-aligned. This creates a clean vertical axis through the table column.
|
||||
- Vertical Alignment: Center-aligned. Text must be positioned exactly in the middle of the cell's height to prevent it from "floating" too close to the top or bottom borders.
|
||||
- Cell Margins (Padding):
|
||||
Left/Right: Set to 120–200 twips (approx. 0.2–0.35 cm). This ensures text does not touch the borders, maintaining legibility.
|
||||
Top/Bottom: Set to at least 60–100 twips to provide a consistent vertical buffer around the text.
|
||||
|
||||
|
||||
### Page break
|
||||
There must be page break between cover page and the content, between table of content and the content also, should NOT put cover page and content in a single page.
|
||||
|
||||
## Page Layout & Margins (A4 Standard)
|
||||
The layout uses a 1440 twip (1 inch) margin for content, with specialized margins for the cover.
|
||||
|
||||
| Section | Top Margin | Bottom/Left/Right | Twips Calculation |
|
||||
|---------------|------------|-------------------|-------------------------------------------|
|
||||
| Cover Page | 0 | 0 | For edge-to-edge background images. |
|
||||
| Main Content | 1800 | 1440 | Extra top space for the header. |
|
||||
| **Twips Unit** | **1 inch = 1440 twips** | **A4 Width = 11906** | **A4 Height = 16838** |
|
||||
|
||||
## Text & Formatting
|
||||
```javascript
|
||||
// IMPORTANT: Never use \n for line breaks - always use separate Paragraph elements
|
||||
// ❌ WRONG: new TextRun("Line 1\nLine 2")
|
||||
// ✅ CORRECT: new Paragraph({ children: [new TextRun("Line 1")] }), new Paragraph({ children: [new TextRun("Line 2")] })
|
||||
|
||||
// First-line indent for body paragraphs
|
||||
// IMPORTANT: Chinese documents typically use 2-character indent (about 480 DXA for 12pt SimSun)
|
||||
new Paragraph({
|
||||
indent: { firstLine: 480 }, // 2-character first-line indent for Chinese body text
|
||||
children: [new TextRun({ text: "This is the main text (Chinese). The first line is indented by two characters.", font: "SimSun" })]
|
||||
})
|
||||
|
||||
// Basic text with all formatting options
|
||||
new Paragraph({
|
||||
alignment: AlignmentType.CENTER,
|
||||
spacing: { before: 200, after: 200 },
|
||||
indent: { left: 720, right: 720, firstLine: 480 }, // Can combine with left/right indent
|
||||
children: [
|
||||
new TextRun({ text: "Bold", bold: true }),
|
||||
new TextRun({ text: "Italic", italics: true }),
|
||||
new TextRun({ text: "Underlined", underline: { type: UnderlineType.DOUBLE, color: "FF0000" } }),
|
||||
new TextRun({ text: "Colored", color: "FF0000", size: 28, font: "Times New Roman" }), // Times New Roman (system font)
|
||||
new TextRun({ text: "Highlighted", highlight: "yellow" }),
|
||||
new TextRun({ text: "Strikethrough", strike: true }),
|
||||
new TextRun({ text: "x2", superScript: true }),
|
||||
new TextRun({ text: "H2O", subScript: true }),
|
||||
new TextRun({ text: "SMALL CAPS", smallCaps: true }),
|
||||
new SymbolRun({ char: "2022", font: "Symbol" }), // Bullet •
|
||||
new SymbolRun({ char: "00A9", font: "Arial" }) // Copyright © - Arial for symbols
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
## Styles & Professional Formatting
|
||||
|
||||
```javascript
|
||||
const doc = new Document({
|
||||
styles: {
|
||||
default: { document: { run: { font: "Times New Roman", size: 24 } } }, // 12pt default (system font)
|
||||
paragraphStyles: [
|
||||
// Document title style - override built-in Title style
|
||||
{ id: "Title", name: "Title", basedOn: "Normal",
|
||||
run: { size: 56, bold: true, color: "000000", font: "Times New Roman" },
|
||||
paragraph: { spacing: { before: 240, after: 120 }, alignment: AlignmentType.CENTER } },
|
||||
// IMPORTANT: Override built-in heading styles by using their exact IDs
|
||||
{ id: "Heading1", name: "Heading 1", basedOn: "Normal", next: "Normal", quickFormat: true,
|
||||
run: { size: 32, bold: true, color: "000000", font: "Times New Roman" }, // 16pt
|
||||
paragraph: { spacing: { before: 240, after: 240 }, outlineLevel: 0 } }, // outlineLevel enables TOC generation if needed
|
||||
{ id: "Heading2", name: "Heading 2", basedOn: "Normal", next: "Normal", quickFormat: true,
|
||||
run: { size: 28, bold: true, color: "000000", font: "Times New Roman" }, // 14pt
|
||||
paragraph: { spacing: { before: 180, after: 180 }, outlineLevel: 1 } },
|
||||
// Custom styles use your own IDs
|
||||
{ id: "myStyle", name: "My Style", basedOn: "Normal",
|
||||
run: { size: 28, bold: true, color: "000000" },
|
||||
paragraph: { spacing: { after: 120 }, alignment: AlignmentType.CENTER } }
|
||||
],
|
||||
characterStyles: [{ id: "myCharStyle", name: "My Char Style",
|
||||
run: { color: "FF0000", bold: true, underline: { type: UnderlineType.SINGLE } } }]
|
||||
},
|
||||
sections: [{
|
||||
properties: { page: { margin: { top: 1440, right: 1440, bottom: 1440, left: 1440 } } },
|
||||
children: [
|
||||
new Paragraph({ heading: HeadingLevel.TITLE, children: [new TextRun("Document Title")] }), // Uses overridden Title style
|
||||
new Paragraph({ heading: HeadingLevel.HEADING_1, children: [new TextRun("Heading 1")] }), // Uses overridden Heading1 style
|
||||
new Paragraph({ style: "myStyle", children: [new TextRun("Custom paragraph style")] }),
|
||||
new Paragraph({ children: [
|
||||
new TextRun("Normal with "),
|
||||
new TextRun({ text: "custom char style", style: "myCharStyle" })
|
||||
]})
|
||||
]
|
||||
}]
|
||||
});
|
||||
```
|
||||
|
||||
**Font Management Strategy (CRITICAL):**
|
||||
|
||||
**ALWAYS prioritize system-installed fonts** for reliability, performance, and cross-platform compatibility:
|
||||
|
||||
1. **System fonts FIRST** (no download, immediate availability):
|
||||
- English: **Times New Roman** (professional standard)
|
||||
- Chinese: **SimSun/宋体** (formal document standard)
|
||||
- Universal fallbacks: Arial, Calibri, Helvetica
|
||||
|
||||
2. **Avoid custom font downloads** unless absolutely necessary for specific branding
|
||||
3. **Test font availability** before deployment
|
||||
|
||||
**Professional Font Combinations (System Fonts Only):**
|
||||
- **Times New Roman (Headers) + Times New Roman (Body)** - Classic, professional, universally supported
|
||||
- **Arial (Headers) + Arial (Body)** - Clean, modern, universally supported
|
||||
- **Times New Roman (Headers) + Arial (Body)** - Classic serif headers with modern body
|
||||
|
||||
**Chinese Document Font Guidelines (System Fonts):**
|
||||
- **Body text**: Use **SimSun/宋体** - the standard system font for Chinese formal documents
|
||||
- **Headings**: Use **SimHei/黑体** - bold sans-serif for visual hierarchy
|
||||
- **Default size**: 12pt (size: 24) for body, 14-16pt for headings
|
||||
- **CRITICAL**: SimSun for body text, SimHei ONLY for headings - never use SimHei for entire document
|
||||
|
||||
```javascript
|
||||
// English document style configuration (Times New Roman)
|
||||
const doc = new Document({
|
||||
styles: {
|
||||
default: { document: { run: { font: "Times New Roman", size: 24 } } }, // 12pt for body
|
||||
paragraphStyles: [
|
||||
{ id: "Heading1", name: "Heading 1", basedOn: "Normal", next: "Normal", quickFormat: true,
|
||||
run: { size: 32, bold: true, font: "Times New Roman" }, // 16pt for H1
|
||||
paragraph: { spacing: { before: 240, after: 240 }, outlineLevel: 0 } },
|
||||
{ id: "Heading2", name: "Heading 2", basedOn: "Normal", next: "Normal", quickFormat: true,
|
||||
run: { size: 28, bold: true, font: "Times New Roman" }, // 14pt for H2
|
||||
paragraph: { spacing: { before: 180, after: 180 }, outlineLevel: 1 } }
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
// Chinese document style configuration (SimSun/SimHei)
|
||||
const doc = new Document({
|
||||
styles: {
|
||||
default: { document: { run: { font: "SimSun", size: 24 } } }, // SimSun 12pt for body
|
||||
paragraphStyles: [
|
||||
{ id: "Heading1", name: "Heading 1", basedOn: "Normal", next: "Normal", quickFormat: true,
|
||||
run: { size: 32, bold: true, font: "SimHei" }, // SimHei 16pt for H1
|
||||
paragraph: { spacing: { before: 240, after: 240 }, outlineLevel: 0 } },
|
||||
{ id: "Heading2", name: "Heading 2", basedOn: "Normal", next: "Normal", quickFormat: true,
|
||||
run: { size: 28, bold: true, font: "SimHei" }, // SimHei 14pt for H2
|
||||
paragraph: { spacing: { before: 180, after: 180 }, outlineLevel: 1 } }
|
||||
]
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
**Key Styling Principles:**
|
||||
- **ALWAYS use system-installed fonts** (Times New Roman for English, SimSun for Chinese)
|
||||
- **Override built-in styles**: Use exact IDs like "Heading1", "Heading2", "Heading3" to override Word's built-in heading styles
|
||||
- **HeadingLevel constants**: `HeadingLevel.HEADING_1` uses "Heading1" style, `HeadingLevel.HEADING_2` uses "Heading2" style, etc.
|
||||
- **outlineLevel**: Set `outlineLevel: 0` for H1, `outlineLevel: 1` for H2, etc. (optional, only needed if TOC will be added)
|
||||
- **Use custom styles** instead of inline formatting for consistency
|
||||
- **Set a default font** using `styles.default.document.run.font` - Times New Roman for English, SimSun for Chinese
|
||||
- **Establish visual hierarchy** with different font sizes (titles > headers > body)
|
||||
- **Add proper spacing** with `before` and `after` paragraph spacing
|
||||
- **Use colors sparingly**: Default to black (000000) and shades of gray for titles and headings (heading 1, heading 2, etc.)
|
||||
- **Set consistent margins** (1440 = 1 inch is standard)
|
||||
|
||||
|
||||
## Lists (ALWAYS USE PROPER LISTS - NEVER USE UNICODE BULLETS)
|
||||
|
||||
### ⚠️ CRITICAL: Numbered List References - Read This Before Creating Lists!
|
||||
|
||||
**Each independently numbered list MUST use a UNIQUE reference name**
|
||||
|
||||
**Rules**:
|
||||
- Same `reference` = continues numbering (1,2,3 → 4,5,6)
|
||||
- Different `reference` = restarts at 1 (1,2,3 → 1,2,3)
|
||||
|
||||
**When to use a new reference?**
|
||||
- ✓ Numbered lists under new headings/sections
|
||||
- ✓ Any list that needs independent numbering
|
||||
- ✗ Subsequent items of the same list (keep using same reference)
|
||||
|
||||
**Reference naming suggestions**:
|
||||
- `list-section-1`, `list-section-2`, `list-section-3`
|
||||
- `list-chapter-1`, `list-chapter-2`
|
||||
- `list-requirements`, `list-constraints` (name based on content)
|
||||
|
||||
```javascript
|
||||
// ❌ WRONG: All lists use the same reference
|
||||
numbering: {
|
||||
config: [
|
||||
{ reference: "my-list", levels: [...] } // Only one config
|
||||
]
|
||||
}
|
||||
// Result:
|
||||
// Chapter 1
|
||||
// 1. Item A
|
||||
// 2. Item B
|
||||
// Chapter 2
|
||||
// 3. Item C ← WRONG! Should start from 1
|
||||
// 4. Item D
|
||||
|
||||
// ✅ CORRECT: Each list uses different reference
|
||||
numbering: {
|
||||
config: [
|
||||
{ reference: "list-chapter-1", levels: [...] },
|
||||
{ reference: "list-chapter-2", levels: [...] },
|
||||
{ reference: "list-chapter-3", levels: [...] }
|
||||
]
|
||||
}
|
||||
// Result:
|
||||
// Chapter 1
|
||||
// 1. Item A
|
||||
// 2. Item B
|
||||
// Chapter 2
|
||||
// 1. Item C ✓ CORRECT! Restarts from 1
|
||||
// 2. Item D
|
||||
// Chapter 3
|
||||
// 1. Item E ✓ CORRECT! Restarts from 1
|
||||
// 2. Item F
|
||||
```
|
||||
|
||||
### Basic List Syntax
|
||||
|
||||
```javascript
|
||||
// Bullets - ALWAYS use the numbering config, NOT unicode symbols
|
||||
// CRITICAL: Use LevelFormat.BULLET constant, NOT the string "bullet"
|
||||
const doc = new Document({
|
||||
numbering: {
|
||||
config: [
|
||||
{ reference: "bullet-list",
|
||||
levels: [{ level: 0, format: LevelFormat.BULLET, text: "•", alignment: AlignmentType.LEFT,
|
||||
style: { paragraph: { indent: { left: 720, hanging: 360 } } } }] },
|
||||
{ reference: "first-numbered-list",
|
||||
levels: [{ level: 0, format: LevelFormat.DECIMAL, text: "%1.", alignment: AlignmentType.LEFT,
|
||||
style: { paragraph: { indent: { left: 720, hanging: 360 } } } }] },
|
||||
{ reference: "second-numbered-list", // Different reference = restarts at 1
|
||||
levels: [{ level: 0, format: LevelFormat.DECIMAL, text: "%1.", alignment: AlignmentType.LEFT,
|
||||
style: { paragraph: { indent: { left: 720, hanging: 360 } } } }] }
|
||||
]
|
||||
},
|
||||
sections: [{
|
||||
children: [
|
||||
// Bullet list items
|
||||
new Paragraph({ numbering: { reference: "bullet-list", level: 0 },
|
||||
children: [new TextRun("First bullet point")] }),
|
||||
new Paragraph({ numbering: { reference: "bullet-list", level: 0 },
|
||||
children: [new TextRun("Second bullet point")] }),
|
||||
// Numbered list items
|
||||
new Paragraph({ numbering: { reference: "first-numbered-list", level: 0 },
|
||||
children: [new TextRun("First numbered item")] }),
|
||||
new Paragraph({ numbering: { reference: "first-numbered-list", level: 0 },
|
||||
children: [new TextRun("Second numbered item")] }),
|
||||
// ⚠️ CRITICAL: Different reference = INDEPENDENT list that restarts at 1
|
||||
// Same reference = CONTINUES previous numbering
|
||||
new Paragraph({ numbering: { reference: "second-numbered-list", level: 0 },
|
||||
children: [new TextRun("Starts at 1 again (because different reference)")] })
|
||||
]
|
||||
}]
|
||||
});
|
||||
|
||||
// ⚠️ CRITICAL: NEVER use unicode bullets - they create fake lists that don't work properly
|
||||
// new TextRun("• Item") // WRONG
|
||||
// new SymbolRun({ char: "2022" }) // WRONG
|
||||
// ✅ ALWAYS use numbering config with LevelFormat.BULLET for real Word lists
|
||||
```
|
||||
|
||||
## Tables
|
||||
```javascript
|
||||
// Complete table with margins, borders, headers, and bullet points
|
||||
const tableBorder = { style: BorderStyle.SINGLE, size: 1, color: "CCCCCC" };
|
||||
const cellBorders = { top: tableBorder, bottom: tableBorder, left: tableBorder, right: tableBorder };
|
||||
|
||||
new Table({
|
||||
columnWidths: [4680, 4680], // ⚠️ CRITICAL: Set column widths at table level - values in DXA (twentieths of a point)
|
||||
// ⚠️ MANDATORY: margins MUST be set to prevent text touching borders
|
||||
margins: { top: 100, bottom: 100, left: 180, right: 180 }, // Minimum comfortable padding
|
||||
rows: [
|
||||
new TableRow({
|
||||
tableHeader: true,
|
||||
children: [
|
||||
new TableCell({
|
||||
borders: cellBorders,
|
||||
width: { size: 4680, type: WidthType.DXA }, // ALSO set width on each cell
|
||||
// ⚠️ CRITICAL: Always use ShadingType.CLEAR to prevent black backgrounds in Word.
|
||||
shading: { fill: "D5E8F0", type: ShadingType.CLEAR },
|
||||
verticalAlign: VerticalAlign.CENTER,
|
||||
children: [new Paragraph({
|
||||
alignment: AlignmentType.CENTER,
|
||||
children: [new TextRun({ text: "Header", bold: true, size: 22 })]
|
||||
})]
|
||||
}),
|
||||
new TableCell({
|
||||
borders: cellBorders,
|
||||
width: { size: 4680, type: WidthType.DXA }, // ALSO set width on each cell
|
||||
shading: { fill: "D5E8F0", type: ShadingType.CLEAR },
|
||||
children: [new Paragraph({
|
||||
alignment: AlignmentType.CENTER,
|
||||
children: [new TextRun({ text: "Bullet Points", bold: true, size: 22 })]
|
||||
})]
|
||||
})
|
||||
]
|
||||
}),
|
||||
new TableRow({
|
||||
children: [
|
||||
new TableCell({
|
||||
borders: cellBorders,
|
||||
width: { size: 4680, type: WidthType.DXA }, // ALSO set width on each cell
|
||||
children: [new Paragraph({ children: [new TextRun("Regular data")] })]
|
||||
}),
|
||||
new TableCell({
|
||||
borders: cellBorders,
|
||||
width: { size: 4680, type: WidthType.DXA }, // ALSO set width on each cell
|
||||
children: [
|
||||
new Paragraph({
|
||||
numbering: { reference: "bullet-list", level: 0 },
|
||||
children: [new TextRun("First bullet point")]
|
||||
}),
|
||||
new Paragraph({
|
||||
numbering: { reference: "bullet-list", level: 0 },
|
||||
children: [new TextRun("Second bullet point")]
|
||||
})
|
||||
]
|
||||
})
|
||||
]
|
||||
})
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
**IMPORTANT: Table Width & Borders**
|
||||
- Use BOTH `columnWidths: [width1, width2, ...]` array AND `width: { size: X, type: WidthType.DXA }` on each cell
|
||||
- Values in DXA (twentieths of a point): 1440 = 1 inch, Letter usable width = 9360 DXA (with 1" margins)
|
||||
- Apply borders to individual `TableCell` elements, NOT the `Table` itself
|
||||
|
||||
**Precomputed Column Widths (Letter size with 1" margins = 9360 DXA total):**
|
||||
- **2 columns:** `columnWidths: [4680, 4680]` (equal width)
|
||||
- **3 columns:** `columnWidths: [3120, 3120, 3120]` (equal width)
|
||||
|
||||
## Links & Navigation
|
||||
```javascript
|
||||
// TOC example
|
||||
// new TableOfContents("Table of Contents", { hyperlink: true, headingStyleRange: "1-3" }),
|
||||
//
|
||||
// CRITICAL: If adding TOC, use HeadingLevel only, NOT custom styles
|
||||
// ❌ WRONG: new Paragraph({ heading: HeadingLevel.HEADING_1, style: "customHeader", children: [new TextRun("Title")] })
|
||||
// ✅ CORRECT: new Paragraph({ heading: HeadingLevel.HEADING_1, children: [new TextRun("Title")] })
|
||||
|
||||
// REQUIRED: After generating the DOCX, add TOC placeholders for first-open experience
|
||||
// Always run: python skills/docx/scripts/add_toc_placeholders.py document.docx --entries '[...]'
|
||||
// This adds placeholder entries that appear before the user updates the TOC (modifies file in-place)
|
||||
// Extract headings from your document to generate accurate entries
|
||||
|
||||
// External link
|
||||
new Paragraph({
|
||||
children: [new ExternalHyperlink({
|
||||
children: [new TextRun({ text: "Google", style: "Hyperlink" })],
|
||||
link: "https://www.google.com"
|
||||
})]
|
||||
}),
|
||||
|
||||
// Internal link & bookmark
|
||||
new Paragraph({
|
||||
children: [new InternalHyperlink({
|
||||
children: [new TextRun({ text: "Go to Section", style: "Hyperlink" })],
|
||||
anchor: "section1"
|
||||
})]
|
||||
}),
|
||||
new Paragraph({
|
||||
children: [new TextRun("Section Content")],
|
||||
bookmark: { id: "section1", name: "section1" }
|
||||
}),
|
||||
|
||||
```
|
||||
|
||||
Use `new Paragraph({ children: [new PageBreak()] })` at the start of the next section to ensure TOC is isolated.
|
||||
|
||||
## Images & Media
|
||||
```javascript
|
||||
// Basic image with sizing & positioning
|
||||
// CRITICAL: Always specify 'type' parameter - it's REQUIRED for ImageRun
|
||||
new Paragraph({
|
||||
alignment: AlignmentType.CENTER,
|
||||
children: [new ImageRun({
|
||||
type: "png", // NEW REQUIREMENT: Must specify image type (png, jpg, jpeg, gif, bmp, svg)
|
||||
data: fs.readFileSync("image.png"),
|
||||
transformation: { width: 200, height: 150, rotation: 0 }, // rotation in degrees
|
||||
altText: { title: "Logo", description: "Company logo", name: "Name" } // IMPORTANT: All three fields are required
|
||||
})]
|
||||
})
|
||||
```
|
||||
|
||||
## Page Breaks
|
||||
```javascript
|
||||
// Manual page break
|
||||
new Paragraph({ children: [new PageBreak()] }),
|
||||
|
||||
// Page break before paragraph
|
||||
new Paragraph({
|
||||
pageBreakBefore: true,
|
||||
children: [new TextRun("This starts on a new page")]
|
||||
})
|
||||
|
||||
// ⚠️ CRITICAL: NEVER use PageBreak standalone - it will create invalid XML that Word cannot open
|
||||
// ❌ WRONG: new PageBreak()
|
||||
// ✅ CORRECT: new Paragraph({ children: [new PageBreak()] })
|
||||
```
|
||||
|
||||
## Cover Page
|
||||
**If the document has a cover page, the cover content should be centered both horizontally and vertically.**
|
||||
|
||||
**Important notes for cover pages:**
|
||||
- **Horizontal centering**: Use `alignment: AlignmentType.CENTER` on all cover page paragraphs
|
||||
- **Vertical centering**: Use `spacing: { before: XXXX }` on elements to visually center content (adjust based on page height)
|
||||
- **Separate section**: Create a dedicated section for the cover page to separate it from main content
|
||||
- **Page break**: Use `new Paragraph({ children: [new PageBreak()] })` at the start of the next section to ensure cover is isolated
|
||||
|
||||
## Headers/Footers & Page Setup
|
||||
```javascript
|
||||
const doc = new Document({
|
||||
sections: [{
|
||||
properties: {
|
||||
page: {
|
||||
margin: { top: 1440, right: 1440, bottom: 1440, left: 1440 }, // 1440 = 1 inch
|
||||
size: { orientation: PageOrientation.LANDSCAPE },
|
||||
pageNumbers: { start: 1, formatType: "decimal" } // "upperRoman", "lowerRoman", "upperLetter", "lowerLetter"
|
||||
}
|
||||
},
|
||||
headers: {
|
||||
default: new Header({ children: [new Paragraph({
|
||||
alignment: AlignmentType.RIGHT,
|
||||
children: [new TextRun("Header Text")]
|
||||
})] })
|
||||
},
|
||||
footers: {
|
||||
default: new Footer({ children: [new Paragraph({
|
||||
alignment: AlignmentType.CENTER,
|
||||
children: [new TextRun("Page "), new TextRun({ children: [PageNumber.CURRENT] }), new TextRun(" of "), new TextRun({ children: [PageNumber.TOTAL_PAGES] })]
|
||||
})] })
|
||||
},
|
||||
children: [/* content */]
|
||||
}]
|
||||
});
|
||||
```
|
||||
|
||||
## Tabs
|
||||
```javascript
|
||||
new Paragraph({
|
||||
tabStops: [
|
||||
{ type: TabStopType.LEFT, position: TabStopPosition.MAX / 4 },
|
||||
{ type: TabStopType.CENTER, position: TabStopPosition.MAX / 2 },
|
||||
{ type: TabStopType.RIGHT, position: TabStopPosition.MAX * 3 / 4 }
|
||||
],
|
||||
children: [new TextRun("Left\tCenter\tRight")]
|
||||
})
|
||||
```
|
||||
|
||||
## Constants & Quick Reference
|
||||
- **Underlines:** `SINGLE`, `DOUBLE`, `WAVY`, `DASH`
|
||||
- **Borders:** `SINGLE`, `DOUBLE`, `DASHED`, `DOTTED`
|
||||
- **Numbering:** `DECIMAL` (1,2,3), `UPPER_ROMAN` (I,II,III), `LOWER_LETTER` (a,b,c)
|
||||
- **Tabs:** `LEFT`, `CENTER`, `RIGHT`, `DECIMAL`
|
||||
- **Symbols:** `"2022"` (•), `"00A9"` (©), `"00AE"` (®), `"2122"` (™), `"00B0"` (°), `"F070"` (✓), `"F0FC"` (✗)
|
||||
|
||||
## Critical Issues & Common Mistakes
|
||||
- **CRITICAL for cover pages**: If the document has a cover page, the cover content should be centered both horizontally (AlignmentType.CENTER) and vertically (use spacing.before to adjust)
|
||||
- **CRITICAL: PageBreak must ALWAYS be inside a Paragraph** - standalone PageBreak creates invalid XML that Word cannot open
|
||||
- **ALWAYS use ShadingType.CLEAR for table cell shading** - Never use ShadingType.SOLID (causes black background).
|
||||
- Measurements in DXA (1440 = 1 inch) | Each table cell needs ≥1 Paragraph | If TOC is added, it requires HeadingLevel styles only
|
||||
- **CRITICAL: ALWAYS use system-installed fonts** - Times New Roman for English, SimSun for Chinese - NEVER download custom fonts unless absolutely necessary
|
||||
- **ALWAYS use custom styles** with appropriate system fonts for professional appearance and proper visual hierarchy
|
||||
- **ALWAYS set a default font** using `styles.default.document.run.font` - **Times New Roman** for English, **SimSun** for Chinese
|
||||
- **CRITICAL for Chinese documents**: Use SimSun for body text, SimHei ONLY for headings - NEVER use SimHei for entire document
|
||||
- **CRITICAL for Chinese body text**: Add first-line indent with `indent: { firstLine: 480 }` (approximately 2 characters for 12pt font)
|
||||
- **ALWAYS use columnWidths array for tables** + individual cell widths for compatibility
|
||||
- **NEVER use unicode symbols for bullets** - always use proper numbering configuration with `LevelFormat.BULLET` constant (NOT the string "bullet")
|
||||
- **NEVER use \n for line breaks anywhere** - always use separate Paragraph elements for each line
|
||||
- **ALWAYS use TextRun objects within Paragraph children** - never use text property directly on Paragraph
|
||||
- **CRITICAL for images**: ImageRun REQUIRES `type` parameter - always specify "png", "jpg", "jpeg", "gif", "bmp", or "svg"
|
||||
- **CRITICAL for bullets**: Must use `LevelFormat.BULLET` constant, not string "bullet", and include `text: "•"` for the bullet character
|
||||
- **CRITICAL for numbering**: Each numbering reference creates an INDEPENDENT list. Same reference = continues numbering (1,2,3 then 4,5,6). Different reference = restarts at 1 (1,2,3 then 1,2,3). Use unique reference names for each separate numbered section!
|
||||
- **CRITICAL for TOC**: When using TableOfContents, headings must use HeadingLevel ONLY - do NOT add custom styles to heading paragraphs or TOC will break.
|
||||
- **CRITICAL for Tables**: Set `columnWidths` array + individual cell widths, apply borders to cells not table
|
||||
- **MANDATORY for Tables**: ALWAYS set `margins` at Table level - this prevents text from touching borders and is required for professional quality. NEVER omit this property.
|
||||
- **Set table margins at TABLE level** for consistent cell padding (avoids repetition per cell)
|
||||
615
skills/docx/ooxml.md
Executable file
615
skills/docx/ooxml.md
Executable file
@@ -0,0 +1,615 @@
|
||||
# Office Open XML Technical Reference
|
||||
|
||||
**Important: Read this entire document before starting.** This document covers:
|
||||
- [Technical Guidelines](#technical-guidelines) - Schema compliance rules and validation requirements
|
||||
- [Document Content Patterns](#document-content-patterns) - XML patterns for headings, lists, tables, formatting, etc.
|
||||
- [Document Library (Python)](#document-library-python) - Recommended approach for OOXML manipulation with automatic infrastructure setup
|
||||
- [Tracked Changes (Redlining)](#tracked-changes-redlining) - XML patterns for implementing tracked changes
|
||||
|
||||
## Technical Guidelines
|
||||
|
||||
### Schema Compliance
|
||||
- **Element ordering in `<w:pPr>`**: `<w:pStyle>`, `<w:numPr>`, `<w:spacing>`, `<w:ind>`, `<w:jc>`
|
||||
- **Whitespace**: Add `xml:space='preserve'` to `<w:t>` elements with leading/trailing spaces
|
||||
- **Unicode**: Escape characters in ASCII content: `"` becomes `“`
|
||||
- **Character encoding reference**: Curly quotes `""` become `“”`, apostrophe `'` becomes `’`, em-dash `—` becomes `—`
|
||||
- **Tracked changes**: Use `<w:del>` and `<w:ins>` tags with `w:author="GLM"` outside `<w:r>` elements
|
||||
- **Critical**: `<w:ins>` closes with `</w:ins>`, `<w:del>` closes with `</w:del>` - never mix
|
||||
- **RSIDs must be 8-digit hex**: Use values like `00AB1234` (only 0-9, A-F characters)
|
||||
- **trackRevisions placement**: Add `<w:trackRevisions/>` after `<w:proofState>` in settings.xml
|
||||
- **Images**: Add to `word/media/`, reference in `document.xml`, set dimensions to prevent overflow
|
||||
|
||||
## Document Content Patterns
|
||||
|
||||
### Basic Structure
|
||||
```xml
|
||||
<w:p>
|
||||
<w:r><w:t>Text content</w:t></w:r>
|
||||
</w:p>
|
||||
```
|
||||
|
||||
### Headings and Styles
|
||||
```xml
|
||||
<w:p>
|
||||
<w:pPr>
|
||||
<w:pStyle w:val="Title"/>
|
||||
<w:jc w:val="center"/>
|
||||
</w:pPr>
|
||||
<w:r><w:t>Document Title</w:t></w:r>
|
||||
</w:p>
|
||||
|
||||
<w:p>
|
||||
<w:pPr><w:pStyle w:val="Heading2"/></w:pPr>
|
||||
<w:r><w:t>Section Heading</w:t></w:r>
|
||||
</w:p>
|
||||
```
|
||||
|
||||
### Text Formatting
|
||||
```xml
|
||||
<!-- Bold -->
|
||||
<w:r><w:rPr><w:b/><w:bCs/></w:rPr><w:t>Bold</w:t></w:r>
|
||||
<!-- Italic -->
|
||||
<w:r><w:rPr><w:i/><w:iCs/></w:rPr><w:t>Italic</w:t></w:r>
|
||||
<!-- Underline -->
|
||||
<w:r><w:rPr><w:u w:val="single"/></w:rPr><w:t>Underlined</w:t></w:r>
|
||||
<!-- Highlight -->
|
||||
<w:r><w:rPr><w:highlight w:val="yellow"/></w:rPr><w:t>Highlighted</w:t></w:r>
|
||||
```
|
||||
|
||||
### Lists
|
||||
```xml
|
||||
<!-- Numbered list -->
|
||||
<w:p>
|
||||
<w:pPr>
|
||||
<w:pStyle w:val="ListParagraph"/>
|
||||
<w:numPr><w:ilvl w:val="0"/><w:numId w:val="1"/></w:numPr>
|
||||
<w:spacing w:before="240"/>
|
||||
</w:pPr>
|
||||
<w:r><w:t>First item</w:t></w:r>
|
||||
</w:p>
|
||||
|
||||
<!-- Restart numbered list at 1 - use different numId -->
|
||||
<w:p>
|
||||
<w:pPr>
|
||||
<w:pStyle w:val="ListParagraph"/>
|
||||
<w:numPr><w:ilvl w:val="0"/><w:numId w:val="2"/></w:numPr>
|
||||
<w:spacing w:before="240"/>
|
||||
</w:pPr>
|
||||
<w:r><w:t>New list item 1</w:t></w:r>
|
||||
</w:p>
|
||||
|
||||
<!-- Bullet list (level 2) -->
|
||||
<w:p>
|
||||
<w:pPr>
|
||||
<w:pStyle w:val="ListParagraph"/>
|
||||
<w:numPr><w:ilvl w:val="1"/><w:numId w:val="1"/></w:numPr>
|
||||
<w:spacing w:before="240"/>
|
||||
<w:ind w:left="900"/>
|
||||
</w:pPr>
|
||||
<w:r><w:t>Bullet item</w:t></w:r>
|
||||
</w:p>
|
||||
```
|
||||
|
||||
### Tables
|
||||
```xml
|
||||
<w:tbl>
|
||||
<w:tblPr>
|
||||
<w:tblStyle w:val="TableGrid"/>
|
||||
<w:tblW w:w="0" w:type="auto"/>
|
||||
</w:tblPr>
|
||||
<w:tblGrid>
|
||||
<w:gridCol w:w="4675"/><w:gridCol w:w="4675"/>
|
||||
</w:tblGrid>
|
||||
<w:tr>
|
||||
<w:tc>
|
||||
<w:tcPr><w:tcW w:w="4675" w:type="dxa"/></w:tcPr>
|
||||
<w:p><w:r><w:t>Cell 1</w:t></w:r></w:p>
|
||||
</w:tc>
|
||||
<w:tc>
|
||||
<w:tcPr><w:tcW w:w="4675" w:type="dxa"/></w:tcPr>
|
||||
<w:p><w:r><w:t>Cell 2</w:t></w:r></w:p>
|
||||
</w:tc>
|
||||
</w:tr>
|
||||
</w:tbl>
|
||||
```
|
||||
|
||||
### Layout
|
||||
```xml
|
||||
<!-- Page break before new section (common pattern) -->
|
||||
<w:p>
|
||||
<w:r>
|
||||
<w:br w:type="page"/>
|
||||
</w:r>
|
||||
</w:p>
|
||||
<w:p>
|
||||
<w:pPr>
|
||||
<w:pStyle w:val="Heading1"/>
|
||||
</w:pPr>
|
||||
<w:r>
|
||||
<w:t>New Section Title</w:t>
|
||||
</w:r>
|
||||
</w:p>
|
||||
|
||||
<!-- Centered paragraph -->
|
||||
<w:p>
|
||||
<w:pPr>
|
||||
<w:spacing w:before="240" w:after="0"/>
|
||||
<w:jc w:val="center"/>
|
||||
</w:pPr>
|
||||
<w:r><w:t>Centered text</w:t></w:r>
|
||||
</w:p>
|
||||
|
||||
<!-- Font change - paragraph level (applies to all runs) -->
|
||||
<w:p>
|
||||
<w:pPr>
|
||||
<w:rPr><w:rFonts w:ascii="Courier New" w:hAnsi="Courier New"/></w:rPr>
|
||||
</w:pPr>
|
||||
<w:r><w:t>Monospace text</w:t></w:r>
|
||||
</w:p>
|
||||
|
||||
<!-- Font change - run level (specific to this text) -->
|
||||
<w:p>
|
||||
<w:r>
|
||||
<w:rPr><w:rFonts w:ascii="Courier New" w:hAnsi="Courier New"/></w:rPr>
|
||||
<w:t>This text is Courier New</w:t>
|
||||
</w:r>
|
||||
<w:r><w:t> and this text uses default font</w:t></w:r>
|
||||
</w:p>
|
||||
```
|
||||
|
||||
## File Updates
|
||||
|
||||
When adding content, update these files:
|
||||
|
||||
**`word/_rels/document.xml.rels`:**
|
||||
```xml
|
||||
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering" Target="numbering.xml"/>
|
||||
<Relationship Id="rId5" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="media/image1.png"/>
|
||||
```
|
||||
|
||||
**`[Content_Types].xml`:**
|
||||
```xml
|
||||
<Default Extension="png" ContentType="image/png"/>
|
||||
<Override PartName="/word/numbering.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml"/>
|
||||
```
|
||||
|
||||
### Images
|
||||
**CRITICAL**: Calculate dimensions to prevent page overflow and maintain aspect ratio.
|
||||
|
||||
```xml
|
||||
<!-- Minimal required structure -->
|
||||
<w:p>
|
||||
<w:r>
|
||||
<w:drawing>
|
||||
<wp:inline>
|
||||
<wp:extent cx="2743200" cy="1828800"/>
|
||||
<wp:docPr id="1" name="Picture 1"/>
|
||||
<a:graphic xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
|
||||
<a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture">
|
||||
<pic:pic xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture">
|
||||
<pic:nvPicPr>
|
||||
<pic:cNvPr id="0" name="image1.png"/>
|
||||
<pic:cNvPicPr/>
|
||||
</pic:nvPicPr>
|
||||
<pic:blipFill>
|
||||
<a:blip r:embed="rId5"/>
|
||||
<!-- Add for stretch fill with aspect ratio preservation -->
|
||||
<a:stretch>
|
||||
<a:fillRect/>
|
||||
</a:stretch>
|
||||
</pic:blipFill>
|
||||
<pic:spPr>
|
||||
<a:xfrm>
|
||||
<a:ext cx="2743200" cy="1828800"/>
|
||||
</a:xfrm>
|
||||
<a:prstGeom prst="rect"/>
|
||||
</pic:spPr>
|
||||
</pic:pic>
|
||||
</a:graphicData>
|
||||
</a:graphic>
|
||||
</wp:inline>
|
||||
</w:drawing>
|
||||
</w:r>
|
||||
</w:p>
|
||||
```
|
||||
|
||||
### Links (Hyperlinks)
|
||||
|
||||
**IMPORTANT**: All hyperlinks (both internal and external) require the Hyperlink style to be defined in styles.xml. Without this style, links will look like regular text instead of blue underlined clickable links.
|
||||
|
||||
**External Links:**
|
||||
```xml
|
||||
<!-- In document.xml -->
|
||||
<w:hyperlink r:id="rId5">
|
||||
<w:r>
|
||||
<w:rPr><w:rStyle w:val="Hyperlink"/></w:rPr>
|
||||
<w:t>Link Text</w:t>
|
||||
</w:r>
|
||||
</w:hyperlink>
|
||||
|
||||
<!-- In word/_rels/document.xml.rels -->
|
||||
<Relationship Id="rId5" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink"
|
||||
Target="https://www.example.com/" TargetMode="External"/>
|
||||
```
|
||||
|
||||
**Internal Links:**
|
||||
|
||||
```xml
|
||||
<!-- Link to bookmark -->
|
||||
<w:hyperlink w:anchor="myBookmark">
|
||||
<w:r>
|
||||
<w:rPr><w:rStyle w:val="Hyperlink"/></w:rPr>
|
||||
<w:t>Link Text</w:t>
|
||||
</w:r>
|
||||
</w:hyperlink>
|
||||
|
||||
<!-- Bookmark target -->
|
||||
<w:bookmarkStart w:id="0" w:name="myBookmark"/>
|
||||
<w:r><w:t>Target content</w:t></w:r>
|
||||
<w:bookmarkEnd w:id="0"/>
|
||||
```
|
||||
|
||||
**Hyperlink Style (required in styles.xml):**
|
||||
```xml
|
||||
<w:style w:type="character" w:styleId="Hyperlink">
|
||||
<w:name w:val="Hyperlink"/>
|
||||
<w:basedOn w:val="DefaultParagraphFont"/>
|
||||
<w:uiPriority w:val="99"/>
|
||||
<w:unhideWhenUsed/>
|
||||
<w:rPr>
|
||||
<w:color w:val="467886" w:themeColor="hyperlink"/>
|
||||
<w:u w:val="single"/>
|
||||
</w:rPr>
|
||||
</w:style>
|
||||
```
|
||||
|
||||
## Document Library (Python)
|
||||
|
||||
Use the Document class from `scripts/document.py` for all tracked changes and comments. It automatically handles infrastructure setup (people.xml, RSIDs, settings.xml, comment files, relationships, content types). Only use direct XML manipulation for complex scenarios not supported by the library.
|
||||
|
||||
**Working with Unicode and Entities:**
|
||||
- **Searching**: Both entity notation and Unicode characters work - `contains="“Company"` and `contains="\u201cCompany"` find the same text
|
||||
- **Replacing**: Use either entities (`“`) or Unicode (`\u201c`) - both work and will be converted appropriately based on the file's encoding (ascii → entities, utf-8 → Unicode)
|
||||
|
||||
### Initialization
|
||||
|
||||
**Find the docx skill root** (directory containing `scripts/` and `ooxml/`):
|
||||
```bash
|
||||
# Search for document.py to locate the skill root
|
||||
# Note: /mnt/skills is used here as an example; check your context for the actual location
|
||||
find /mnt/skills -name "document.py" -path "*/docx/scripts/*" 2>/dev/null | head -1
|
||||
# Example output: /mnt/skills/docx/scripts/document.py
|
||||
# Skill root is: /mnt/skills/docx
|
||||
```
|
||||
|
||||
**Run your script with PYTHONPATH** set to the docx skill root:
|
||||
```bash
|
||||
PYTHONPATH=/mnt/skills/docx python your_script.py
|
||||
```
|
||||
|
||||
**In your script**, import from the skill root:
|
||||
```python
|
||||
from scripts.document import Document, DocxXMLEditor
|
||||
|
||||
# Basic initialization (automatically creates temp copy and sets up infrastructure)
|
||||
doc = Document('unpacked')
|
||||
|
||||
# Customize author and initials
|
||||
doc = Document('unpacked', author="John Doe", initials="JD")
|
||||
|
||||
# Enable track revisions mode
|
||||
doc = Document('unpacked', track_revisions=True)
|
||||
|
||||
# Specify custom RSID (auto-generated if not provided)
|
||||
doc = Document('unpacked', rsid="07DC5ECB")
|
||||
```
|
||||
|
||||
### Creating Tracked Changes
|
||||
|
||||
**CRITICAL**: Only mark text that actually changes. Keep ALL unchanged text outside `<w:del>`/`<w:ins>` tags. Marking unchanged text makes edits unprofessional and harder to review.
|
||||
|
||||
**Attribute Handling**: The Document class auto-injects attributes (w:id, w:date, w:rsidR, w:rsidDel, w16du:dateUtc, xml:space) into new elements. When preserving unchanged text from the original document, copy the original `<w:r>` element with its existing attributes to maintain document integrity.
|
||||
|
||||
**Method Selection Guide**:
|
||||
- **Adding your own changes to regular text**: Use `replace_node()` with `<w:del>`/`<w:ins>` tags, or `suggest_deletion()` for removing entire `<w:r>` or `<w:p>` elements
|
||||
- **Partially modifying another author's tracked change**: Use `replace_node()` to nest your changes inside their `<w:ins>`/`<w:del>`
|
||||
- **Completely rejecting another author's insertion**: Use `revert_insertion()` on the `<w:ins>` element (NOT `suggest_deletion()`)
|
||||
- **Completely rejecting another author's deletion**: Use `revert_deletion()` on the `<w:del>` element to restore deleted content using tracked changes
|
||||
|
||||
```python
|
||||
# Minimal edit - change one word: "The report is monthly" → "The report is quarterly"
|
||||
# Original: <w:r w:rsidR="00AB12CD"><w:rPr><w:rFonts w:ascii="Calibri"/></w:rPr><w:t>The report is monthly</w:t></w:r>
|
||||
node = doc["word/document.xml"].get_node(tag="w:r", contains="The report is monthly")
|
||||
rpr = tags[0].toxml() if (tags := node.getElementsByTagName("w:rPr")) else ""
|
||||
replacement = f'<w:r w:rsidR="00AB12CD">{rpr}<w:t>The report is </w:t></w:r><w:del><w:r>{rpr}<w:delText>monthly</w:delText></w:r></w:del><w:ins><w:r>{rpr}<w:t>quarterly</w:t></w:r></w:ins>'
|
||||
doc["word/document.xml"].replace_node(node, replacement)
|
||||
|
||||
# Minimal edit - change number: "within 30 days" → "within 45 days"
|
||||
# Original: <w:r w:rsidR="00XYZ789"><w:rPr><w:rFonts w:ascii="Calibri"/></w:rPr><w:t>within 30 days</w:t></w:r>
|
||||
node = doc["word/document.xml"].get_node(tag="w:r", contains="within 30 days")
|
||||
rpr = tags[0].toxml() if (tags := node.getElementsByTagName("w:rPr")) else ""
|
||||
replacement = f'<w:r w:rsidR="00XYZ789">{rpr}<w:t>within </w:t></w:r><w:del><w:r>{rpr}<w:delText>30</w:delText></w:r></w:del><w:ins><w:r>{rpr}<w:t>45</w:t></w:r></w:ins><w:r w:rsidR="00XYZ789">{rpr}<w:t> days</w:t></w:r>'
|
||||
doc["word/document.xml"].replace_node(node, replacement)
|
||||
|
||||
# Complete replacement - preserve formatting even when replacing all text
|
||||
node = doc["word/document.xml"].get_node(tag="w:r", contains="apple")
|
||||
rpr = tags[0].toxml() if (tags := node.getElementsByTagName("w:rPr")) else ""
|
||||
replacement = f'<w:del><w:r>{rpr}<w:delText>apple</w:delText></w:r></w:del><w:ins><w:r>{rpr}<w:t>banana orange</w:t></w:r></w:ins>'
|
||||
doc["word/document.xml"].replace_node(node, replacement)
|
||||
|
||||
# Insert new content (no attributes needed - auto-injected)
|
||||
node = doc["word/document.xml"].get_node(tag="w:r", contains="existing text")
|
||||
doc["word/document.xml"].insert_after(node, '<w:ins><w:r><w:t>new text</w:t></w:r></w:ins>')
|
||||
|
||||
# Partially delete another author's insertion
|
||||
# Original: <w:ins w:author="Jane Smith" w:date="..."><w:r><w:t>quarterly financial report</w:t></w:r></w:ins>
|
||||
# Goal: Delete only "financial" to make it "quarterly report"
|
||||
node = doc["word/document.xml"].get_node(tag="w:ins", attrs={"w:id": "5"})
|
||||
# IMPORTANT: Preserve w:author="Jane Smith" on the outer <w:ins> to maintain authorship
|
||||
replacement = '''<w:ins w:author="Jane Smith" w:date="2025-01-15T10:00:00Z">
|
||||
<w:r><w:t>quarterly </w:t></w:r>
|
||||
<w:del><w:r><w:delText>financial </w:delText></w:r></w:del>
|
||||
<w:r><w:t>report</w:t></w:r>
|
||||
</w:ins>'''
|
||||
doc["word/document.xml"].replace_node(node, replacement)
|
||||
|
||||
# Change part of another author's insertion
|
||||
# Original: <w:ins w:author="Jane Smith"><w:r><w:t>in silence, safe and sound</w:t></w:r></w:ins>
|
||||
# Goal: Change "safe and sound" to "soft and unbound"
|
||||
node = doc["word/document.xml"].get_node(tag="w:ins", attrs={"w:id": "8"})
|
||||
replacement = f'''<w:ins w:author="Jane Smith" w:date="2025-01-15T10:00:00Z">
|
||||
<w:r><w:t>in silence, </w:t></w:r>
|
||||
</w:ins>
|
||||
<w:ins>
|
||||
<w:r><w:t>soft and unbound</w:t></w:r>
|
||||
</w:ins>
|
||||
<w:ins w:author="Jane Smith" w:date="2025-01-15T10:00:00Z">
|
||||
<w:del><w:r><w:delText>safe and sound</w:delText></w:r></w:del>
|
||||
</w:ins>'''
|
||||
doc["word/document.xml"].replace_node(node, replacement)
|
||||
|
||||
# Delete entire run (use only when deleting all content; use replace_node for partial deletions)
|
||||
node = doc["word/document.xml"].get_node(tag="w:r", contains="text to delete")
|
||||
doc["word/document.xml"].suggest_deletion(node)
|
||||
|
||||
# Delete entire paragraph (in-place, handles both regular and numbered list paragraphs)
|
||||
para = doc["word/document.xml"].get_node(tag="w:p", contains="paragraph to delete")
|
||||
doc["word/document.xml"].suggest_deletion(para)
|
||||
|
||||
# Add new numbered list item
|
||||
target_para = doc["word/document.xml"].get_node(tag="w:p", contains="existing list item")
|
||||
pPr = tags[0].toxml() if (tags := target_para.getElementsByTagName("w:pPr")) else ""
|
||||
new_item = f'<w:p>{pPr}<w:r><w:t>New item</w:t></w:r></w:p>'
|
||||
tracked_para = DocxXMLEditor.suggest_paragraph(new_item)
|
||||
doc["word/document.xml"].insert_after(target_para, tracked_para)
|
||||
# Optional: add spacing paragraph before content for better visual separation
|
||||
# spacing = DocxXMLEditor.suggest_paragraph('<w:p><w:pPr><w:pStyle w:val="ListParagraph"/></w:pPr></w:p>')
|
||||
# doc["word/document.xml"].insert_after(target_para, spacing + tracked_para)
|
||||
```
|
||||
|
||||
### Adding Comments
|
||||
|
||||
Comments are added with the author name "Z.ai" by default. Initialize the Document with custom author if needed:
|
||||
|
||||
```python
|
||||
# Initialize with Z.ai as author (recommended)
|
||||
doc = Document('unpacked', author="Z.ai", initials="Z")
|
||||
|
||||
# Add comment spanning two existing tracked changes
|
||||
# Note: w:id is auto-generated. Only search by w:id if you know it from XML inspection
|
||||
start_node = doc["word/document.xml"].get_node(tag="w:del", attrs={"w:id": "1"})
|
||||
end_node = doc["word/document.xml"].get_node(tag="w:ins", attrs={"w:id": "2"})
|
||||
doc.add_comment(start=start_node, end=end_node, text="Explanation of this change")
|
||||
|
||||
# Add comment on a paragraph
|
||||
para = doc["word/document.xml"].get_node(tag="w:p", contains="paragraph text")
|
||||
doc.add_comment(start=para, end=para, text="Comment on this paragraph")
|
||||
|
||||
# Add comment on newly created tracked change
|
||||
# First create the tracked change
|
||||
node = doc["word/document.xml"].get_node(tag="w:r", contains="old")
|
||||
new_nodes = doc["word/document.xml"].replace_node(
|
||||
node,
|
||||
'<w:del><w:r><w:delText>old</w:delText></w:r></w:del><w:ins><w:r><w:t>new</w:t></w:r></w:ins>'
|
||||
)
|
||||
# Then add comment on the newly created elements
|
||||
# new_nodes[0] is the <w:del>, new_nodes[1] is the <w:ins>
|
||||
doc.add_comment(start=new_nodes[0], end=new_nodes[1], text="Changed old to new per requirements")
|
||||
|
||||
# Reply to existing comment
|
||||
doc.reply_to_comment(parent_comment_id=0, text="I agree with this change")
|
||||
```
|
||||
|
||||
### Rejecting Tracked Changes
|
||||
|
||||
**IMPORTANT**: Use `revert_insertion()` to reject insertions and `revert_deletion()` to restore deletions using tracked changes. Use `suggest_deletion()` only for regular unmarked content.
|
||||
|
||||
```python
|
||||
# Reject insertion (wraps it in deletion)
|
||||
# Use this when another author inserted text that you want to delete
|
||||
ins = doc["word/document.xml"].get_node(tag="w:ins", attrs={"w:id": "5"})
|
||||
nodes = doc["word/document.xml"].revert_insertion(ins) # Returns [ins]
|
||||
|
||||
# Reject deletion (creates insertion to restore deleted content)
|
||||
# Use this when another author deleted text that you want to restore
|
||||
del_elem = doc["word/document.xml"].get_node(tag="w:del", attrs={"w:id": "3"})
|
||||
nodes = doc["word/document.xml"].revert_deletion(del_elem) # Returns [del_elem, new_ins]
|
||||
|
||||
# Reject all insertions in a paragraph
|
||||
para = doc["word/document.xml"].get_node(tag="w:p", contains="paragraph text")
|
||||
nodes = doc["word/document.xml"].revert_insertion(para) # Returns [para]
|
||||
|
||||
# Reject all deletions in a paragraph
|
||||
para = doc["word/document.xml"].get_node(tag="w:p", contains="paragraph text")
|
||||
nodes = doc["word/document.xml"].revert_deletion(para) # Returns [para]
|
||||
```
|
||||
|
||||
### Inserting Images
|
||||
|
||||
**CRITICAL**: The Document class works with a temporary copy at `doc.unpacked_path`. Always copy images to this temp directory, not the original unpacked folder.
|
||||
|
||||
```python
|
||||
from PIL import Image
|
||||
import shutil, os
|
||||
|
||||
# Initialize document first
|
||||
doc = Document('unpacked')
|
||||
|
||||
# Copy image and calculate full-width dimensions with aspect ratio
|
||||
media_dir = os.path.join(doc.unpacked_path, 'word/media')
|
||||
os.makedirs(media_dir, exist_ok=True)
|
||||
shutil.copy('image.png', os.path.join(media_dir, 'image1.png'))
|
||||
img = Image.open(os.path.join(media_dir, 'image1.png'))
|
||||
width_emus = int(6.5 * 914400) # 6.5" usable width, 914400 EMUs/inch
|
||||
height_emus = int(width_emus * img.size[1] / img.size[0])
|
||||
|
||||
# Add relationship and content type
|
||||
rels_editor = doc['word/_rels/document.xml.rels']
|
||||
next_rid = rels_editor.get_next_rid()
|
||||
rels_editor.append_to(rels_editor.dom.documentElement,
|
||||
f'<Relationship Id="{next_rid}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="media/image1.png"/>')
|
||||
doc['[Content_Types].xml'].append_to(doc['[Content_Types].xml'].dom.documentElement,
|
||||
'<Default Extension="png" ContentType="image/png"/>')
|
||||
|
||||
# Insert image
|
||||
node = doc["word/document.xml"].get_node(tag="w:p", line_number=100)
|
||||
doc["word/document.xml"].insert_after(node, f'''<w:p>
|
||||
<w:r>
|
||||
<w:drawing>
|
||||
<wp:inline distT="0" distB="0" distL="0" distR="0">
|
||||
<wp:extent cx="{width_emus}" cy="{height_emus}"/>
|
||||
<wp:docPr id="1" name="Picture 1"/>
|
||||
<a:graphic xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
|
||||
<a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture">
|
||||
<pic:pic xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture">
|
||||
<pic:nvPicPr><pic:cNvPr id="1" name="image1.png"/><pic:cNvPicPr/></pic:nvPicPr>
|
||||
<pic:blipFill><a:blip r:embed="{next_rid}"/><a:stretch><a:fillRect/></a:stretch></pic:blipFill>
|
||||
<pic:spPr><a:xfrm><a:ext cx="{width_emus}" cy="{height_emus}"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></pic:spPr>
|
||||
</pic:pic>
|
||||
</a:graphicData>
|
||||
</a:graphic>
|
||||
</wp:inline>
|
||||
</w:drawing>
|
||||
</w:r>
|
||||
</w:p>''')
|
||||
```
|
||||
|
||||
### Getting Nodes
|
||||
|
||||
```python
|
||||
# By text content
|
||||
node = doc["word/document.xml"].get_node(tag="w:p", contains="specific text")
|
||||
|
||||
# By line range
|
||||
para = doc["word/document.xml"].get_node(tag="w:p", line_number=range(100, 150))
|
||||
|
||||
# By attributes
|
||||
node = doc["word/document.xml"].get_node(tag="w:del", attrs={"w:id": "1"})
|
||||
|
||||
# By exact line number (must be line number where tag opens)
|
||||
para = doc["word/document.xml"].get_node(tag="w:p", line_number=42)
|
||||
|
||||
# Combine filters
|
||||
node = doc["word/document.xml"].get_node(tag="w:r", line_number=range(40, 60), contains="text")
|
||||
|
||||
# Disambiguate when text appears multiple times - add line_number range
|
||||
node = doc["word/document.xml"].get_node(tag="w:r", contains="Section", line_number=range(2400, 2500))
|
||||
```
|
||||
|
||||
### Saving
|
||||
|
||||
```python
|
||||
# Save with automatic validation (copies back to original directory)
|
||||
doc.save() # Validates by default, raises error if validation fails
|
||||
|
||||
# Save to different location
|
||||
doc.save('modified-unpacked')
|
||||
|
||||
# Skip validation (debugging only - needing this in production indicates XML issues)
|
||||
doc.save(validate=False)
|
||||
```
|
||||
|
||||
### Direct DOM Manipulation
|
||||
|
||||
For complex scenarios not covered by the library:
|
||||
|
||||
```python
|
||||
# Access any XML file
|
||||
editor = doc["word/document.xml"]
|
||||
editor = doc["word/comments.xml"]
|
||||
|
||||
# Direct DOM access (defusedxml.minidom.Document)
|
||||
node = doc["word/document.xml"].get_node(tag="w:p", line_number=5)
|
||||
parent = node.parentNode
|
||||
parent.removeChild(node)
|
||||
parent.appendChild(node) # Move to end
|
||||
|
||||
# General document manipulation (without tracked changes)
|
||||
old_node = doc["word/document.xml"].get_node(tag="w:p", contains="original text")
|
||||
doc["word/document.xml"].replace_node(old_node, "<w:p><w:r><w:t>replacement text</w:t></w:r></w:p>")
|
||||
|
||||
# Multiple insertions - use return value to maintain order
|
||||
node = doc["word/document.xml"].get_node(tag="w:r", line_number=100)
|
||||
nodes = doc["word/document.xml"].insert_after(node, "<w:r><w:t>A</w:t></w:r>")
|
||||
nodes = doc["word/document.xml"].insert_after(nodes[-1], "<w:r><w:t>B</w:t></w:r>")
|
||||
nodes = doc["word/document.xml"].insert_after(nodes[-1], "<w:r><w:t>C</w:t></w:r>")
|
||||
# Results in: original_node, A, B, C
|
||||
```
|
||||
|
||||
## Tracked Changes (Redlining)
|
||||
|
||||
**Use the Document class above for all tracked changes.** The patterns below are for reference when constructing replacement XML strings.
|
||||
|
||||
### Validation Rules
|
||||
The validator checks that the document text matches the original after reverting GLM's changes. This means:
|
||||
- **NEVER modify text inside another author's `<w:ins>` or `<w:del>` tags**
|
||||
- **ALWAYS use nested deletions** to remove another author's insertions
|
||||
- **Every edit must be properly tracked** with `<w:ins>` or `<w:del>` tags
|
||||
|
||||
### Tracked Change Patterns
|
||||
|
||||
**CRITICAL RULES**:
|
||||
1. Never modify the content inside another author's tracked changes. Always use nested deletions.
|
||||
2. **XML Structure**: Always place `<w:del>` and `<w:ins>` at paragraph level containing complete `<w:r>` elements. Never nest inside `<w:r>` elements - this creates invalid XML that breaks document processing.
|
||||
|
||||
**Text Insertion:**
|
||||
```xml
|
||||
<w:ins w:id="1" w:author="GLM" w:date="2025-07-30T23:05:00Z" w16du:dateUtc="2025-07-31T06:05:00Z">
|
||||
<w:r w:rsidR="00792858">
|
||||
<w:t>inserted text</w:t>
|
||||
</w:r>
|
||||
</w:ins>
|
||||
```
|
||||
|
||||
**Text Deletion:**
|
||||
```xml
|
||||
<w:del w:id="2" w:author="GLM" w:date="2025-07-30T23:05:00Z" w16du:dateUtc="2025-07-31T06:05:00Z">
|
||||
<w:r w:rsidDel="00792858">
|
||||
<w:delText>deleted text</w:delText>
|
||||
</w:r>
|
||||
</w:del>
|
||||
```
|
||||
|
||||
**Deleting Another Author's Insertion (MUST use nested structure):**
|
||||
```xml
|
||||
<!-- Nest deletion inside the original insertion -->
|
||||
<w:ins w:author="Jane Smith" w:id="16">
|
||||
<w:del w:author="GLM" w:id="40">
|
||||
<w:r><w:delText>monthly</w:delText></w:r>
|
||||
</w:del>
|
||||
</w:ins>
|
||||
<w:ins w:author="GLM" w:id="41">
|
||||
<w:r><w:t>weekly</w:t></w:r>
|
||||
</w:ins>
|
||||
```
|
||||
|
||||
**Restoring Another Author's Deletion:**
|
||||
```xml
|
||||
<!-- Leave their deletion unchanged, add new insertion after it -->
|
||||
<w:del w:author="Jane Smith" w:id="50">
|
||||
<w:r><w:delText>within 30 days</w:delText></w:r>
|
||||
</w:del>
|
||||
<w:ins w:author="GLM" w:id="51">
|
||||
<w:r><w:t>within 30 days</w:t></w:r>
|
||||
</w:ins>
|
||||
```
|
||||
1499
skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd
Executable file
1499
skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd
Executable file
File diff suppressed because it is too large
Load Diff
146
skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd
Executable file
146
skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd
Executable file
@@ -0,0 +1,146 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
|
||||
xmlns="http://schemas.openxmlformats.org/drawingml/2006/chartDrawing"
|
||||
targetNamespace="http://schemas.openxmlformats.org/drawingml/2006/chartDrawing"
|
||||
elementFormDefault="qualified">
|
||||
<xsd:import namespace="http://schemas.openxmlformats.org/drawingml/2006/main"
|
||||
schemaLocation="dml-main.xsd"/>
|
||||
<xsd:complexType name="CT_ShapeNonVisual">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="cNvSpPr" type="a:CT_NonVisualDrawingShapeProps" minOccurs="1" maxOccurs="1"
|
||||
/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Shape">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="nvSpPr" type="CT_ShapeNonVisual" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="spPr" type="a:CT_ShapeProperties" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="style" type="a:CT_ShapeStyle" minOccurs="0" maxOccurs="1"/>
|
||||
<xsd:element name="txBody" type="a:CT_TextBody" minOccurs="0" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="macro" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="textlink" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="fLocksText" type="xsd:boolean" use="optional" default="true"/>
|
||||
<xsd:attribute name="fPublished" type="xsd:boolean" use="optional" default="false"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_ConnectorNonVisual">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="cNvCxnSpPr" type="a:CT_NonVisualConnectorProperties" minOccurs="1"
|
||||
maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Connector">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="nvCxnSpPr" type="CT_ConnectorNonVisual" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="spPr" type="a:CT_ShapeProperties" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="style" type="a:CT_ShapeStyle" minOccurs="0" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="macro" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="fPublished" type="xsd:boolean" use="optional" default="false"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_PictureNonVisual">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="cNvPicPr" type="a:CT_NonVisualPictureProperties" minOccurs="1"
|
||||
maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Picture">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="nvPicPr" type="CT_PictureNonVisual" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="blipFill" type="a:CT_BlipFillProperties" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="spPr" type="a:CT_ShapeProperties" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="style" type="a:CT_ShapeStyle" minOccurs="0" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="macro" type="xsd:string" use="optional" default=""/>
|
||||
<xsd:attribute name="fPublished" type="xsd:boolean" use="optional" default="false"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_GraphicFrameNonVisual">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="cNvGraphicFramePr" type="a:CT_NonVisualGraphicFrameProperties"
|
||||
minOccurs="1" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_GraphicFrame">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="nvGraphicFramePr" type="CT_GraphicFrameNonVisual" minOccurs="1"
|
||||
maxOccurs="1"/>
|
||||
<xsd:element name="xfrm" type="a:CT_Transform2D" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element ref="a:graphic" minOccurs="1" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="macro" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="fPublished" type="xsd:boolean" use="optional" default="false"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_GroupShapeNonVisual">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="cNvGrpSpPr" type="a:CT_NonVisualGroupDrawingShapeProps" minOccurs="1"
|
||||
maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_GroupShape">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="nvGrpSpPr" type="CT_GroupShapeNonVisual" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="grpSpPr" type="a:CT_GroupShapeProperties" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:choice minOccurs="0" maxOccurs="unbounded">
|
||||
<xsd:element name="sp" type="CT_Shape"/>
|
||||
<xsd:element name="grpSp" type="CT_GroupShape"/>
|
||||
<xsd:element name="graphicFrame" type="CT_GraphicFrame"/>
|
||||
<xsd:element name="cxnSp" type="CT_Connector"/>
|
||||
<xsd:element name="pic" type="CT_Picture"/>
|
||||
</xsd:choice>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:group name="EG_ObjectChoices">
|
||||
<xsd:sequence>
|
||||
<xsd:choice minOccurs="1" maxOccurs="1">
|
||||
<xsd:element name="sp" type="CT_Shape"/>
|
||||
<xsd:element name="grpSp" type="CT_GroupShape"/>
|
||||
<xsd:element name="graphicFrame" type="CT_GraphicFrame"/>
|
||||
<xsd:element name="cxnSp" type="CT_Connector"/>
|
||||
<xsd:element name="pic" type="CT_Picture"/>
|
||||
</xsd:choice>
|
||||
</xsd:sequence>
|
||||
</xsd:group>
|
||||
<xsd:simpleType name="ST_MarkerCoordinate">
|
||||
<xsd:restriction base="xsd:double">
|
||||
<xsd:minInclusive value="0.0"/>
|
||||
<xsd:maxInclusive value="1.0"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_Marker">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="x" type="ST_MarkerCoordinate" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="y" type="ST_MarkerCoordinate" minOccurs="1" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_RelSizeAnchor">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="from" type="CT_Marker"/>
|
||||
<xsd:element name="to" type="CT_Marker"/>
|
||||
<xsd:group ref="EG_ObjectChoices"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_AbsSizeAnchor">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="from" type="CT_Marker"/>
|
||||
<xsd:element name="ext" type="a:CT_PositiveSize2D"/>
|
||||
<xsd:group ref="EG_ObjectChoices"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:group name="EG_Anchor">
|
||||
<xsd:choice>
|
||||
<xsd:element name="relSizeAnchor" type="CT_RelSizeAnchor"/>
|
||||
<xsd:element name="absSizeAnchor" type="CT_AbsSizeAnchor"/>
|
||||
</xsd:choice>
|
||||
</xsd:group>
|
||||
<xsd:complexType name="CT_Drawing">
|
||||
<xsd:sequence>
|
||||
<xsd:group ref="EG_Anchor" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
</xsd:schema>
|
||||
1085
skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd
Executable file
1085
skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd
Executable file
File diff suppressed because it is too large
Load Diff
11
skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd
Executable file
11
skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd
Executable file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns="http://schemas.openxmlformats.org/drawingml/2006/lockedCanvas"
|
||||
xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
|
||||
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
|
||||
elementFormDefault="qualified"
|
||||
targetNamespace="http://schemas.openxmlformats.org/drawingml/2006/lockedCanvas">
|
||||
<xsd:import namespace="http://schemas.openxmlformats.org/drawingml/2006/main"
|
||||
schemaLocation="dml-main.xsd"/>
|
||||
<xsd:element name="lockedCanvas" type="a:CT_GvmlGroupShape"/>
|
||||
</xsd:schema>
|
||||
3081
skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd
Executable file
3081
skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd
Executable file
File diff suppressed because it is too large
Load Diff
23
skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd
Executable file
23
skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd
Executable file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns="http://schemas.openxmlformats.org/drawingml/2006/picture"
|
||||
xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" elementFormDefault="qualified"
|
||||
targetNamespace="http://schemas.openxmlformats.org/drawingml/2006/picture">
|
||||
<xsd:import namespace="http://schemas.openxmlformats.org/drawingml/2006/main"
|
||||
schemaLocation="dml-main.xsd"/>
|
||||
<xsd:complexType name="CT_PictureNonVisual">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="cNvPicPr" type="a:CT_NonVisualPictureProperties" minOccurs="1"
|
||||
maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Picture">
|
||||
<xsd:sequence minOccurs="1" maxOccurs="1">
|
||||
<xsd:element name="nvPicPr" type="CT_PictureNonVisual" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="blipFill" type="a:CT_BlipFillProperties" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="spPr" type="a:CT_ShapeProperties" minOccurs="1" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:element name="pic" type="CT_Picture"/>
|
||||
</xsd:schema>
|
||||
185
skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd
Executable file
185
skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd
Executable file
@@ -0,0 +1,185 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
|
||||
xmlns="http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"
|
||||
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
|
||||
targetNamespace="http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"
|
||||
elementFormDefault="qualified">
|
||||
<xsd:import namespace="http://schemas.openxmlformats.org/drawingml/2006/main"
|
||||
schemaLocation="dml-main.xsd"/>
|
||||
<xsd:import schemaLocation="shared-relationshipReference.xsd"
|
||||
namespace="http://schemas.openxmlformats.org/officeDocument/2006/relationships"/>
|
||||
<xsd:element name="from" type="CT_Marker"/>
|
||||
<xsd:element name="to" type="CT_Marker"/>
|
||||
<xsd:complexType name="CT_AnchorClientData">
|
||||
<xsd:attribute name="fLocksWithSheet" type="xsd:boolean" use="optional" default="true"/>
|
||||
<xsd:attribute name="fPrintsWithSheet" type="xsd:boolean" use="optional" default="true"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_ShapeNonVisual">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="cNvSpPr" type="a:CT_NonVisualDrawingShapeProps" minOccurs="1" maxOccurs="1"
|
||||
/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Shape">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="nvSpPr" type="CT_ShapeNonVisual" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="spPr" type="a:CT_ShapeProperties" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="style" type="a:CT_ShapeStyle" minOccurs="0" maxOccurs="1"/>
|
||||
<xsd:element name="txBody" type="a:CT_TextBody" minOccurs="0" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="macro" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="textlink" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="fLocksText" type="xsd:boolean" use="optional" default="true"/>
|
||||
<xsd:attribute name="fPublished" type="xsd:boolean" use="optional" default="false"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_ConnectorNonVisual">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="cNvCxnSpPr" type="a:CT_NonVisualConnectorProperties" minOccurs="1"
|
||||
maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Connector">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="nvCxnSpPr" type="CT_ConnectorNonVisual" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="spPr" type="a:CT_ShapeProperties" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="style" type="a:CT_ShapeStyle" minOccurs="0" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="macro" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="fPublished" type="xsd:boolean" use="optional" default="false"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_PictureNonVisual">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="cNvPicPr" type="a:CT_NonVisualPictureProperties" minOccurs="1"
|
||||
maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Picture">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="nvPicPr" type="CT_PictureNonVisual" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="blipFill" type="a:CT_BlipFillProperties" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="spPr" type="a:CT_ShapeProperties" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="style" type="a:CT_ShapeStyle" minOccurs="0" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="macro" type="xsd:string" use="optional" default=""/>
|
||||
<xsd:attribute name="fPublished" type="xsd:boolean" use="optional" default="false"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_GraphicalObjectFrameNonVisual">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="cNvGraphicFramePr" type="a:CT_NonVisualGraphicFrameProperties"
|
||||
minOccurs="1" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_GraphicalObjectFrame">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="nvGraphicFramePr" type="CT_GraphicalObjectFrameNonVisual" minOccurs="1"
|
||||
maxOccurs="1"/>
|
||||
<xsd:element name="xfrm" type="a:CT_Transform2D" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element ref="a:graphic" minOccurs="1" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="macro" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="fPublished" type="xsd:boolean" use="optional" default="false"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_GroupShapeNonVisual">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="cNvGrpSpPr" type="a:CT_NonVisualGroupDrawingShapeProps" minOccurs="1"
|
||||
maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_GroupShape">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="nvGrpSpPr" type="CT_GroupShapeNonVisual" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="grpSpPr" type="a:CT_GroupShapeProperties" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:choice minOccurs="0" maxOccurs="unbounded">
|
||||
<xsd:element name="sp" type="CT_Shape"/>
|
||||
<xsd:element name="grpSp" type="CT_GroupShape"/>
|
||||
<xsd:element name="graphicFrame" type="CT_GraphicalObjectFrame"/>
|
||||
<xsd:element name="cxnSp" type="CT_Connector"/>
|
||||
<xsd:element name="pic" type="CT_Picture"/>
|
||||
</xsd:choice>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:group name="EG_ObjectChoices">
|
||||
<xsd:sequence>
|
||||
<xsd:choice minOccurs="1" maxOccurs="1">
|
||||
<xsd:element name="sp" type="CT_Shape"/>
|
||||
<xsd:element name="grpSp" type="CT_GroupShape"/>
|
||||
<xsd:element name="graphicFrame" type="CT_GraphicalObjectFrame"/>
|
||||
<xsd:element name="cxnSp" type="CT_Connector"/>
|
||||
<xsd:element name="pic" type="CT_Picture"/>
|
||||
<xsd:element name="contentPart" type="CT_Rel"/>
|
||||
</xsd:choice>
|
||||
</xsd:sequence>
|
||||
</xsd:group>
|
||||
<xsd:complexType name="CT_Rel">
|
||||
<xsd:attribute ref="r:id" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_ColID">
|
||||
<xsd:restriction base="xsd:int">
|
||||
<xsd:minInclusive value="0"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_RowID">
|
||||
<xsd:restriction base="xsd:int">
|
||||
<xsd:minInclusive value="0"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_Marker">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="col" type="ST_ColID"/>
|
||||
<xsd:element name="colOff" type="a:ST_Coordinate"/>
|
||||
<xsd:element name="row" type="ST_RowID"/>
|
||||
<xsd:element name="rowOff" type="a:ST_Coordinate"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_EditAs">
|
||||
<xsd:restriction base="xsd:token">
|
||||
<xsd:enumeration value="twoCell"/>
|
||||
<xsd:enumeration value="oneCell"/>
|
||||
<xsd:enumeration value="absolute"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_TwoCellAnchor">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="from" type="CT_Marker"/>
|
||||
<xsd:element name="to" type="CT_Marker"/>
|
||||
<xsd:group ref="EG_ObjectChoices"/>
|
||||
<xsd:element name="clientData" type="CT_AnchorClientData" minOccurs="1" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="editAs" type="ST_EditAs" use="optional" default="twoCell"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_OneCellAnchor">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="from" type="CT_Marker"/>
|
||||
<xsd:element name="ext" type="a:CT_PositiveSize2D"/>
|
||||
<xsd:group ref="EG_ObjectChoices"/>
|
||||
<xsd:element name="clientData" type="CT_AnchorClientData" minOccurs="1" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_AbsoluteAnchor">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="pos" type="a:CT_Point2D"/>
|
||||
<xsd:element name="ext" type="a:CT_PositiveSize2D"/>
|
||||
<xsd:group ref="EG_ObjectChoices"/>
|
||||
<xsd:element name="clientData" type="CT_AnchorClientData" minOccurs="1" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:group name="EG_Anchor">
|
||||
<xsd:choice>
|
||||
<xsd:element name="twoCellAnchor" type="CT_TwoCellAnchor"/>
|
||||
<xsd:element name="oneCellAnchor" type="CT_OneCellAnchor"/>
|
||||
<xsd:element name="absoluteAnchor" type="CT_AbsoluteAnchor"/>
|
||||
</xsd:choice>
|
||||
</xsd:group>
|
||||
<xsd:complexType name="CT_Drawing">
|
||||
<xsd:sequence>
|
||||
<xsd:group ref="EG_Anchor" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:element name="wsDr" type="CT_Drawing"/>
|
||||
</xsd:schema>
|
||||
287
skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd
Executable file
287
skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd
Executable file
@@ -0,0 +1,287 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
|
||||
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
|
||||
xmlns:dpct="http://schemas.openxmlformats.org/drawingml/2006/picture"
|
||||
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
|
||||
xmlns="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"
|
||||
targetNamespace="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"
|
||||
elementFormDefault="qualified">
|
||||
<xsd:import namespace="http://schemas.openxmlformats.org/drawingml/2006/main"
|
||||
schemaLocation="dml-main.xsd"/>
|
||||
<xsd:import schemaLocation="wml.xsd"
|
||||
namespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main"/>
|
||||
<xsd:import namespace="http://schemas.openxmlformats.org/drawingml/2006/picture"
|
||||
schemaLocation="dml-picture.xsd"/>
|
||||
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
|
||||
schemaLocation="shared-relationshipReference.xsd"/>
|
||||
<xsd:complexType name="CT_EffectExtent">
|
||||
<xsd:attribute name="l" type="a:ST_Coordinate" use="required"/>
|
||||
<xsd:attribute name="t" type="a:ST_Coordinate" use="required"/>
|
||||
<xsd:attribute name="r" type="a:ST_Coordinate" use="required"/>
|
||||
<xsd:attribute name="b" type="a:ST_Coordinate" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_WrapDistance">
|
||||
<xsd:restriction base="xsd:unsignedInt"/>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_Inline">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="extent" type="a:CT_PositiveSize2D"/>
|
||||
<xsd:element name="effectExtent" type="CT_EffectExtent" minOccurs="0"/>
|
||||
<xsd:element name="docPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="cNvGraphicFramePr" type="a:CT_NonVisualGraphicFrameProperties"
|
||||
minOccurs="0" maxOccurs="1"/>
|
||||
<xsd:element ref="a:graphic" minOccurs="1" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="distT" type="ST_WrapDistance" use="optional"/>
|
||||
<xsd:attribute name="distB" type="ST_WrapDistance" use="optional"/>
|
||||
<xsd:attribute name="distL" type="ST_WrapDistance" use="optional"/>
|
||||
<xsd:attribute name="distR" type="ST_WrapDistance" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_WrapText">
|
||||
<xsd:restriction base="xsd:token">
|
||||
<xsd:enumeration value="bothSides"/>
|
||||
<xsd:enumeration value="left"/>
|
||||
<xsd:enumeration value="right"/>
|
||||
<xsd:enumeration value="largest"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_WrapPath">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="start" type="a:CT_Point2D" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="lineTo" type="a:CT_Point2D" minOccurs="2" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="edited" type="xsd:boolean" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_WrapNone"/>
|
||||
<xsd:complexType name="CT_WrapSquare">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="effectExtent" type="CT_EffectExtent" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="wrapText" type="ST_WrapText" use="required"/>
|
||||
<xsd:attribute name="distT" type="ST_WrapDistance" use="optional"/>
|
||||
<xsd:attribute name="distB" type="ST_WrapDistance" use="optional"/>
|
||||
<xsd:attribute name="distL" type="ST_WrapDistance" use="optional"/>
|
||||
<xsd:attribute name="distR" type="ST_WrapDistance" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_WrapTight">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="wrapPolygon" type="CT_WrapPath" minOccurs="1" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="wrapText" type="ST_WrapText" use="required"/>
|
||||
<xsd:attribute name="distL" type="ST_WrapDistance" use="optional"/>
|
||||
<xsd:attribute name="distR" type="ST_WrapDistance" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_WrapThrough">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="wrapPolygon" type="CT_WrapPath" minOccurs="1" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="wrapText" type="ST_WrapText" use="required"/>
|
||||
<xsd:attribute name="distL" type="ST_WrapDistance" use="optional"/>
|
||||
<xsd:attribute name="distR" type="ST_WrapDistance" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_WrapTopBottom">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="effectExtent" type="CT_EffectExtent" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="distT" type="ST_WrapDistance" use="optional"/>
|
||||
<xsd:attribute name="distB" type="ST_WrapDistance" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:group name="EG_WrapType">
|
||||
<xsd:sequence>
|
||||
<xsd:choice minOccurs="1" maxOccurs="1">
|
||||
<xsd:element name="wrapNone" type="CT_WrapNone" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="wrapSquare" type="CT_WrapSquare" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="wrapTight" type="CT_WrapTight" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="wrapThrough" type="CT_WrapThrough" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="wrapTopAndBottom" type="CT_WrapTopBottom" minOccurs="1" maxOccurs="1"/>
|
||||
</xsd:choice>
|
||||
</xsd:sequence>
|
||||
</xsd:group>
|
||||
<xsd:simpleType name="ST_PositionOffset">
|
||||
<xsd:restriction base="xsd:int"/>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_AlignH">
|
||||
<xsd:restriction base="xsd:token">
|
||||
<xsd:enumeration value="left"/>
|
||||
<xsd:enumeration value="right"/>
|
||||
<xsd:enumeration value="center"/>
|
||||
<xsd:enumeration value="inside"/>
|
||||
<xsd:enumeration value="outside"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_RelFromH">
|
||||
<xsd:restriction base="xsd:token">
|
||||
<xsd:enumeration value="margin"/>
|
||||
<xsd:enumeration value="page"/>
|
||||
<xsd:enumeration value="column"/>
|
||||
<xsd:enumeration value="character"/>
|
||||
<xsd:enumeration value="leftMargin"/>
|
||||
<xsd:enumeration value="rightMargin"/>
|
||||
<xsd:enumeration value="insideMargin"/>
|
||||
<xsd:enumeration value="outsideMargin"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_PosH">
|
||||
<xsd:sequence>
|
||||
<xsd:choice minOccurs="1" maxOccurs="1">
|
||||
<xsd:element name="align" type="ST_AlignH" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="posOffset" type="ST_PositionOffset" minOccurs="1" maxOccurs="1"/>
|
||||
</xsd:choice>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="relativeFrom" type="ST_RelFromH" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_AlignV">
|
||||
<xsd:restriction base="xsd:token">
|
||||
<xsd:enumeration value="top"/>
|
||||
<xsd:enumeration value="bottom"/>
|
||||
<xsd:enumeration value="center"/>
|
||||
<xsd:enumeration value="inside"/>
|
||||
<xsd:enumeration value="outside"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_RelFromV">
|
||||
<xsd:restriction base="xsd:token">
|
||||
<xsd:enumeration value="margin"/>
|
||||
<xsd:enumeration value="page"/>
|
||||
<xsd:enumeration value="paragraph"/>
|
||||
<xsd:enumeration value="line"/>
|
||||
<xsd:enumeration value="topMargin"/>
|
||||
<xsd:enumeration value="bottomMargin"/>
|
||||
<xsd:enumeration value="insideMargin"/>
|
||||
<xsd:enumeration value="outsideMargin"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_PosV">
|
||||
<xsd:sequence>
|
||||
<xsd:choice minOccurs="1" maxOccurs="1">
|
||||
<xsd:element name="align" type="ST_AlignV" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="posOffset" type="ST_PositionOffset" minOccurs="1" maxOccurs="1"/>
|
||||
</xsd:choice>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="relativeFrom" type="ST_RelFromV" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Anchor">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="simplePos" type="a:CT_Point2D"/>
|
||||
<xsd:element name="positionH" type="CT_PosH"/>
|
||||
<xsd:element name="positionV" type="CT_PosV"/>
|
||||
<xsd:element name="extent" type="a:CT_PositiveSize2D"/>
|
||||
<xsd:element name="effectExtent" type="CT_EffectExtent" minOccurs="0"/>
|
||||
<xsd:group ref="EG_WrapType"/>
|
||||
<xsd:element name="docPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="cNvGraphicFramePr" type="a:CT_NonVisualGraphicFrameProperties"
|
||||
minOccurs="0" maxOccurs="1"/>
|
||||
<xsd:element ref="a:graphic" minOccurs="1" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="distT" type="ST_WrapDistance" use="optional"/>
|
||||
<xsd:attribute name="distB" type="ST_WrapDistance" use="optional"/>
|
||||
<xsd:attribute name="distL" type="ST_WrapDistance" use="optional"/>
|
||||
<xsd:attribute name="distR" type="ST_WrapDistance" use="optional"/>
|
||||
<xsd:attribute name="simplePos" type="xsd:boolean"/>
|
||||
<xsd:attribute name="relativeHeight" type="xsd:unsignedInt" use="required"/>
|
||||
<xsd:attribute name="behindDoc" type="xsd:boolean" use="required"/>
|
||||
<xsd:attribute name="locked" type="xsd:boolean" use="required"/>
|
||||
<xsd:attribute name="layoutInCell" type="xsd:boolean" use="required"/>
|
||||
<xsd:attribute name="hidden" type="xsd:boolean" use="optional"/>
|
||||
<xsd:attribute name="allowOverlap" type="xsd:boolean" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_TxbxContent">
|
||||
<xsd:group ref="w:EG_BlockLevelElts" minOccurs="1" maxOccurs="unbounded"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_TextboxInfo">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="txbxContent" type="CT_TxbxContent" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="extLst" type="a:CT_OfficeArtExtensionList" minOccurs="0" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="id" type="xsd:unsignedShort" use="optional" default="0"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_LinkedTextboxInformation">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="extLst" type="a:CT_OfficeArtExtensionList" minOccurs="0" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="id" type="xsd:unsignedShort" use="required"/>
|
||||
<xsd:attribute name="seq" type="xsd:unsignedShort" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_WordprocessingShape">
|
||||
<xsd:sequence minOccurs="1" maxOccurs="1">
|
||||
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="0" maxOccurs="1"/>
|
||||
<xsd:choice minOccurs="1" maxOccurs="1">
|
||||
<xsd:element name="cNvSpPr" type="a:CT_NonVisualDrawingShapeProps" minOccurs="1"
|
||||
maxOccurs="1"/>
|
||||
<xsd:element name="cNvCnPr" type="a:CT_NonVisualConnectorProperties" minOccurs="1"
|
||||
maxOccurs="1"/>
|
||||
</xsd:choice>
|
||||
<xsd:element name="spPr" type="a:CT_ShapeProperties" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="style" type="a:CT_ShapeStyle" minOccurs="0" maxOccurs="1"/>
|
||||
<xsd:element name="extLst" type="a:CT_OfficeArtExtensionList" minOccurs="0" maxOccurs="1"/>
|
||||
<xsd:choice minOccurs="0" maxOccurs="1">
|
||||
<xsd:element name="txbx" type="CT_TextboxInfo" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="linkedTxbx" type="CT_LinkedTextboxInformation" minOccurs="1"
|
||||
maxOccurs="1"/>
|
||||
</xsd:choice>
|
||||
<xsd:element name="bodyPr" type="a:CT_TextBodyProperties" minOccurs="1" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="normalEastAsianFlow" type="xsd:boolean" use="optional" default="false"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_GraphicFrame">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="cNvFrPr" type="a:CT_NonVisualGraphicFrameProperties" minOccurs="1"
|
||||
maxOccurs="1"/>
|
||||
<xsd:element name="xfrm" type="a:CT_Transform2D" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element ref="a:graphic" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="extLst" type="a:CT_OfficeArtExtensionList" minOccurs="0" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_WordprocessingContentPartNonVisual">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="0" maxOccurs="1"/>
|
||||
<xsd:element name="cNvContentPartPr" type="a:CT_NonVisualContentPartProperties" minOccurs="0" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_WordprocessingContentPart">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="nvContentPartPr" type="CT_WordprocessingContentPartNonVisual" minOccurs="0" maxOccurs="1"/>
|
||||
<xsd:element name="xfrm" type="a:CT_Transform2D" minOccurs="0" maxOccurs="1"/>
|
||||
<xsd:element name="extLst" type="a:CT_OfficeArtExtensionList" minOccurs="0" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="bwMode" type="a:ST_BlackWhiteMode" use="optional"/>
|
||||
<xsd:attribute ref="r:id" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_WordprocessingGroup">
|
||||
<xsd:sequence minOccurs="1" maxOccurs="1">
|
||||
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="0" maxOccurs="1"/>
|
||||
<xsd:element name="cNvGrpSpPr" type="a:CT_NonVisualGroupDrawingShapeProps" minOccurs="1"
|
||||
maxOccurs="1"/>
|
||||
<xsd:element name="grpSpPr" type="a:CT_GroupShapeProperties" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:choice minOccurs="0" maxOccurs="unbounded">
|
||||
<xsd:element ref="wsp"/>
|
||||
<xsd:element name="grpSp" type="CT_WordprocessingGroup"/>
|
||||
<xsd:element name="graphicFrame" type="CT_GraphicFrame"/>
|
||||
<xsd:element ref="dpct:pic"/>
|
||||
<xsd:element name="contentPart" type="CT_WordprocessingContentPart"/>
|
||||
</xsd:choice>
|
||||
<xsd:element name="extLst" type="a:CT_OfficeArtExtensionList" minOccurs="0" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_WordprocessingCanvas">
|
||||
<xsd:sequence minOccurs="1" maxOccurs="1">
|
||||
<xsd:element name="bg" type="a:CT_BackgroundFormatting" minOccurs="0" maxOccurs="1"/>
|
||||
<xsd:element name="whole" type="a:CT_WholeE2oFormatting" minOccurs="0" maxOccurs="1"/>
|
||||
<xsd:choice minOccurs="0" maxOccurs="unbounded">
|
||||
<xsd:element ref="wsp"/>
|
||||
<xsd:element ref="dpct:pic"/>
|
||||
<xsd:element name="contentPart" type="CT_WordprocessingContentPart"/>
|
||||
<xsd:element ref="wgp"/>
|
||||
<xsd:element name="graphicFrame" type="CT_GraphicFrame"/>
|
||||
</xsd:choice>
|
||||
<xsd:element name="extLst" type="a:CT_OfficeArtExtensionList" minOccurs="0" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:element name="wpc" type="CT_WordprocessingCanvas"/>
|
||||
<xsd:element name="wgp" type="CT_WordprocessingGroup"/>
|
||||
<xsd:element name="wsp" type="CT_WordprocessingShape"/>
|
||||
<xsd:element name="inline" type="CT_Inline"/>
|
||||
<xsd:element name="anchor" type="CT_Anchor"/>
|
||||
</xsd:schema>
|
||||
1676
skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd
Executable file
1676
skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd
Executable file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns="http://schemas.openxmlformats.org/officeDocument/2006/characteristics"
|
||||
targetNamespace="http://schemas.openxmlformats.org/officeDocument/2006/characteristics"
|
||||
elementFormDefault="qualified">
|
||||
<xsd:complexType name="CT_AdditionalCharacteristics">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="characteristic" type="CT_Characteristic" minOccurs="0"
|
||||
maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Characteristic">
|
||||
<xsd:attribute name="name" type="xsd:string" use="required"/>
|
||||
<xsd:attribute name="relation" type="ST_Relation" use="required"/>
|
||||
<xsd:attribute name="val" type="xsd:string" use="required"/>
|
||||
<xsd:attribute name="vocabulary" type="xsd:anyURI" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_Relation">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="ge"/>
|
||||
<xsd:enumeration value="le"/>
|
||||
<xsd:enumeration value="gt"/>
|
||||
<xsd:enumeration value="lt"/>
|
||||
<xsd:enumeration value="eq"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:element name="additionalCharacteristics" type="CT_AdditionalCharacteristics"/>
|
||||
</xsd:schema>
|
||||
144
skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd
Executable file
144
skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd
Executable file
@@ -0,0 +1,144 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns="http://schemas.openxmlformats.org/officeDocument/2006/bibliography"
|
||||
xmlns:s="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
|
||||
targetNamespace="http://schemas.openxmlformats.org/officeDocument/2006/bibliography"
|
||||
elementFormDefault="qualified">
|
||||
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
|
||||
schemaLocation="shared-commonSimpleTypes.xsd"/>
|
||||
<xsd:simpleType name="ST_SourceType">
|
||||
<xsd:restriction base="s:ST_String">
|
||||
<xsd:enumeration value="ArticleInAPeriodical"/>
|
||||
<xsd:enumeration value="Book"/>
|
||||
<xsd:enumeration value="BookSection"/>
|
||||
<xsd:enumeration value="JournalArticle"/>
|
||||
<xsd:enumeration value="ConferenceProceedings"/>
|
||||
<xsd:enumeration value="Report"/>
|
||||
<xsd:enumeration value="SoundRecording"/>
|
||||
<xsd:enumeration value="Performance"/>
|
||||
<xsd:enumeration value="Art"/>
|
||||
<xsd:enumeration value="DocumentFromInternetSite"/>
|
||||
<xsd:enumeration value="InternetSite"/>
|
||||
<xsd:enumeration value="Film"/>
|
||||
<xsd:enumeration value="Interview"/>
|
||||
<xsd:enumeration value="Patent"/>
|
||||
<xsd:enumeration value="ElectronicSource"/>
|
||||
<xsd:enumeration value="Case"/>
|
||||
<xsd:enumeration value="Misc"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_NameListType">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="Person" type="CT_PersonType" minOccurs="1" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_PersonType">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="Last" type="s:ST_String" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xsd:element name="First" type="s:ST_String" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xsd:element name="Middle" type="s:ST_String" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_NameType">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="NameList" type="CT_NameListType" minOccurs="1" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_NameOrCorporateType">
|
||||
<xsd:sequence>
|
||||
<xsd:choice minOccurs="0" maxOccurs="1">
|
||||
<xsd:element name="NameList" type="CT_NameListType" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="Corporate" minOccurs="1" maxOccurs="1" type="s:ST_String"/>
|
||||
</xsd:choice>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_AuthorType">
|
||||
<xsd:sequence>
|
||||
<xsd:choice minOccurs="0" maxOccurs="unbounded">
|
||||
<xsd:element name="Artist" type="CT_NameType"/>
|
||||
<xsd:element name="Author" type="CT_NameOrCorporateType"/>
|
||||
<xsd:element name="BookAuthor" type="CT_NameType"/>
|
||||
<xsd:element name="Compiler" type="CT_NameType"/>
|
||||
<xsd:element name="Composer" type="CT_NameType"/>
|
||||
<xsd:element name="Conductor" type="CT_NameType"/>
|
||||
<xsd:element name="Counsel" type="CT_NameType"/>
|
||||
<xsd:element name="Director" type="CT_NameType"/>
|
||||
<xsd:element name="Editor" type="CT_NameType"/>
|
||||
<xsd:element name="Interviewee" type="CT_NameType"/>
|
||||
<xsd:element name="Interviewer" type="CT_NameType"/>
|
||||
<xsd:element name="Inventor" type="CT_NameType"/>
|
||||
<xsd:element name="Performer" type="CT_NameOrCorporateType"/>
|
||||
<xsd:element name="ProducerName" type="CT_NameType"/>
|
||||
<xsd:element name="Translator" type="CT_NameType"/>
|
||||
<xsd:element name="Writer" type="CT_NameType"/>
|
||||
</xsd:choice>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_SourceType">
|
||||
<xsd:sequence>
|
||||
<xsd:choice minOccurs="0" maxOccurs="unbounded">
|
||||
<xsd:element name="AbbreviatedCaseNumber" type="s:ST_String"/>
|
||||
<xsd:element name="AlbumTitle" type="s:ST_String"/>
|
||||
<xsd:element name="Author" type="CT_AuthorType"/>
|
||||
<xsd:element name="BookTitle" type="s:ST_String"/>
|
||||
<xsd:element name="Broadcaster" type="s:ST_String"/>
|
||||
<xsd:element name="BroadcastTitle" type="s:ST_String"/>
|
||||
<xsd:element name="CaseNumber" type="s:ST_String"/>
|
||||
<xsd:element name="ChapterNumber" type="s:ST_String"/>
|
||||
<xsd:element name="City" type="s:ST_String"/>
|
||||
<xsd:element name="Comments" type="s:ST_String"/>
|
||||
<xsd:element name="ConferenceName" type="s:ST_String"/>
|
||||
<xsd:element name="CountryRegion" type="s:ST_String"/>
|
||||
<xsd:element name="Court" type="s:ST_String"/>
|
||||
<xsd:element name="Day" type="s:ST_String"/>
|
||||
<xsd:element name="DayAccessed" type="s:ST_String"/>
|
||||
<xsd:element name="Department" type="s:ST_String"/>
|
||||
<xsd:element name="Distributor" type="s:ST_String"/>
|
||||
<xsd:element name="Edition" type="s:ST_String"/>
|
||||
<xsd:element name="Guid" type="s:ST_String"/>
|
||||
<xsd:element name="Institution" type="s:ST_String"/>
|
||||
<xsd:element name="InternetSiteTitle" type="s:ST_String"/>
|
||||
<xsd:element name="Issue" type="s:ST_String"/>
|
||||
<xsd:element name="JournalName" type="s:ST_String"/>
|
||||
<xsd:element name="LCID" type="s:ST_Lang"/>
|
||||
<xsd:element name="Medium" type="s:ST_String"/>
|
||||
<xsd:element name="Month" type="s:ST_String"/>
|
||||
<xsd:element name="MonthAccessed" type="s:ST_String"/>
|
||||
<xsd:element name="NumberVolumes" type="s:ST_String"/>
|
||||
<xsd:element name="Pages" type="s:ST_String"/>
|
||||
<xsd:element name="PatentNumber" type="s:ST_String"/>
|
||||
<xsd:element name="PeriodicalTitle" type="s:ST_String"/>
|
||||
<xsd:element name="ProductionCompany" type="s:ST_String"/>
|
||||
<xsd:element name="PublicationTitle" type="s:ST_String"/>
|
||||
<xsd:element name="Publisher" type="s:ST_String"/>
|
||||
<xsd:element name="RecordingNumber" type="s:ST_String"/>
|
||||
<xsd:element name="RefOrder" type="s:ST_String"/>
|
||||
<xsd:element name="Reporter" type="s:ST_String"/>
|
||||
<xsd:element name="SourceType" type="ST_SourceType"/>
|
||||
<xsd:element name="ShortTitle" type="s:ST_String"/>
|
||||
<xsd:element name="StandardNumber" type="s:ST_String"/>
|
||||
<xsd:element name="StateProvince" type="s:ST_String"/>
|
||||
<xsd:element name="Station" type="s:ST_String"/>
|
||||
<xsd:element name="Tag" type="s:ST_String"/>
|
||||
<xsd:element name="Theater" type="s:ST_String"/>
|
||||
<xsd:element name="ThesisType" type="s:ST_String"/>
|
||||
<xsd:element name="Title" type="s:ST_String"/>
|
||||
<xsd:element name="Type" type="s:ST_String"/>
|
||||
<xsd:element name="URL" type="s:ST_String"/>
|
||||
<xsd:element name="Version" type="s:ST_String"/>
|
||||
<xsd:element name="Volume" type="s:ST_String"/>
|
||||
<xsd:element name="Year" type="s:ST_String"/>
|
||||
<xsd:element name="YearAccessed" type="s:ST_String"/>
|
||||
</xsd:choice>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:element name="Sources" type="CT_Sources"/>
|
||||
<xsd:complexType name="CT_Sources">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="Source" type="CT_SourceType" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="SelectedStyle" type="s:ST_String"/>
|
||||
<xsd:attribute name="StyleName" type="s:ST_String"/>
|
||||
<xsd:attribute name="URI" type="s:ST_String"/>
|
||||
</xsd:complexType>
|
||||
</xsd:schema>
|
||||
174
skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd
Executable file
174
skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd
Executable file
@@ -0,0 +1,174 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
|
||||
targetNamespace="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
|
||||
elementFormDefault="qualified">
|
||||
<xsd:simpleType name="ST_Lang">
|
||||
<xsd:restriction base="xsd:string"/>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_HexColorRGB">
|
||||
<xsd:restriction base="xsd:hexBinary">
|
||||
<xsd:length value="3" fixed="true"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_Panose">
|
||||
<xsd:restriction base="xsd:hexBinary">
|
||||
<xsd:length value="10"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_CalendarType">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="gregorian"/>
|
||||
<xsd:enumeration value="gregorianUs"/>
|
||||
<xsd:enumeration value="gregorianMeFrench"/>
|
||||
<xsd:enumeration value="gregorianArabic"/>
|
||||
<xsd:enumeration value="hijri"/>
|
||||
<xsd:enumeration value="hebrew"/>
|
||||
<xsd:enumeration value="taiwan"/>
|
||||
<xsd:enumeration value="japan"/>
|
||||
<xsd:enumeration value="thai"/>
|
||||
<xsd:enumeration value="korea"/>
|
||||
<xsd:enumeration value="saka"/>
|
||||
<xsd:enumeration value="gregorianXlitEnglish"/>
|
||||
<xsd:enumeration value="gregorianXlitFrench"/>
|
||||
<xsd:enumeration value="none"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_AlgClass">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="hash"/>
|
||||
<xsd:enumeration value="custom"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_CryptProv">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="rsaAES"/>
|
||||
<xsd:enumeration value="rsaFull"/>
|
||||
<xsd:enumeration value="custom"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_AlgType">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="typeAny"/>
|
||||
<xsd:enumeration value="custom"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_ColorType">
|
||||
<xsd:restriction base="xsd:string"/>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_Guid">
|
||||
<xsd:restriction base="xsd:token">
|
||||
<xsd:pattern value="\{[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}\}"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_OnOff">
|
||||
<xsd:union memberTypes="xsd:boolean ST_OnOff1"/>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_OnOff1">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="on"/>
|
||||
<xsd:enumeration value="off"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_String">
|
||||
<xsd:restriction base="xsd:string"/>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_XmlName">
|
||||
<xsd:restriction base="xsd:NCName">
|
||||
<xsd:minLength value="1"/>
|
||||
<xsd:maxLength value="255"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_TrueFalse">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="t"/>
|
||||
<xsd:enumeration value="f"/>
|
||||
<xsd:enumeration value="true"/>
|
||||
<xsd:enumeration value="false"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_TrueFalseBlank">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="t"/>
|
||||
<xsd:enumeration value="f"/>
|
||||
<xsd:enumeration value="true"/>
|
||||
<xsd:enumeration value="false"/>
|
||||
<xsd:enumeration value=""/>
|
||||
<xsd:enumeration value="True"/>
|
||||
<xsd:enumeration value="False"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_UnsignedDecimalNumber">
|
||||
<xsd:restriction base="xsd:decimal">
|
||||
<xsd:minInclusive value="0"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_TwipsMeasure">
|
||||
<xsd:union memberTypes="ST_UnsignedDecimalNumber ST_PositiveUniversalMeasure"/>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_VerticalAlignRun">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="baseline"/>
|
||||
<xsd:enumeration value="superscript"/>
|
||||
<xsd:enumeration value="subscript"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_Xstring">
|
||||
<xsd:restriction base="xsd:string"/>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_XAlign">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="left"/>
|
||||
<xsd:enumeration value="center"/>
|
||||
<xsd:enumeration value="right"/>
|
||||
<xsd:enumeration value="inside"/>
|
||||
<xsd:enumeration value="outside"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_YAlign">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="inline"/>
|
||||
<xsd:enumeration value="top"/>
|
||||
<xsd:enumeration value="center"/>
|
||||
<xsd:enumeration value="bottom"/>
|
||||
<xsd:enumeration value="inside"/>
|
||||
<xsd:enumeration value="outside"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_ConformanceClass">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="strict"/>
|
||||
<xsd:enumeration value="transitional"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_UniversalMeasure">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:pattern value="-?[0-9]+(\.[0-9]+)?(mm|cm|in|pt|pc|pi)"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_PositiveUniversalMeasure">
|
||||
<xsd:restriction base="ST_UniversalMeasure">
|
||||
<xsd:pattern value="[0-9]+(\.[0-9]+)?(mm|cm|in|pt|pc|pi)"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_Percentage">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:pattern value="-?[0-9]+(\.[0-9]+)?%"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_FixedPercentage">
|
||||
<xsd:restriction base="ST_Percentage">
|
||||
<xsd:pattern value="-?((100)|([0-9][0-9]?))(\.[0-9][0-9]?)?%"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_PositivePercentage">
|
||||
<xsd:restriction base="ST_Percentage">
|
||||
<xsd:pattern value="[0-9]+(\.[0-9]+)?%"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_PositiveFixedPercentage">
|
||||
<xsd:restriction base="ST_Percentage">
|
||||
<xsd:pattern value="((100)|([0-9][0-9]?))(\.[0-9][0-9]?)?%"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
</xsd:schema>
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns="http://schemas.openxmlformats.org/officeDocument/2006/customXml"
|
||||
xmlns:s="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
|
||||
targetNamespace="http://schemas.openxmlformats.org/officeDocument/2006/customXml"
|
||||
elementFormDefault="qualified" attributeFormDefault="qualified" blockDefault="#all">
|
||||
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
|
||||
schemaLocation="shared-commonSimpleTypes.xsd"/>
|
||||
<xsd:complexType name="CT_DatastoreSchemaRef">
|
||||
<xsd:attribute name="uri" type="xsd:string" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_DatastoreSchemaRefs">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="schemaRef" type="CT_DatastoreSchemaRef" minOccurs="0" maxOccurs="unbounded"
|
||||
/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_DatastoreItem">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="schemaRefs" type="CT_DatastoreSchemaRefs" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="itemID" type="s:ST_Guid" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:element name="datastoreItem" type="CT_DatastoreItem"/>
|
||||
</xsd:schema>
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns="http://schemas.openxmlformats.org/schemaLibrary/2006/main"
|
||||
targetNamespace="http://schemas.openxmlformats.org/schemaLibrary/2006/main"
|
||||
attributeFormDefault="qualified" elementFormDefault="qualified">
|
||||
<xsd:complexType name="CT_Schema">
|
||||
<xsd:attribute name="uri" type="xsd:string" default=""/>
|
||||
<xsd:attribute name="manifestLocation" type="xsd:string"/>
|
||||
<xsd:attribute name="schemaLocation" type="xsd:string"/>
|
||||
<xsd:attribute name="schemaLanguage" type="xsd:token"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_SchemaLibrary">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="schema" type="CT_Schema" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:element name="schemaLibrary" type="CT_SchemaLibrary"/>
|
||||
</xsd:schema>
|
||||
@@ -0,0 +1,59 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns="http://schemas.openxmlformats.org/officeDocument/2006/custom-properties"
|
||||
xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"
|
||||
xmlns:s="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
|
||||
targetNamespace="http://schemas.openxmlformats.org/officeDocument/2006/custom-properties"
|
||||
blockDefault="#all" elementFormDefault="qualified">
|
||||
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"
|
||||
schemaLocation="shared-documentPropertiesVariantTypes.xsd"/>
|
||||
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
|
||||
schemaLocation="shared-commonSimpleTypes.xsd"/>
|
||||
<xsd:element name="Properties" type="CT_Properties"/>
|
||||
<xsd:complexType name="CT_Properties">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="property" minOccurs="0" maxOccurs="unbounded" type="CT_Property"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Property">
|
||||
<xsd:choice minOccurs="1" maxOccurs="1">
|
||||
<xsd:element ref="vt:vector"/>
|
||||
<xsd:element ref="vt:array"/>
|
||||
<xsd:element ref="vt:blob"/>
|
||||
<xsd:element ref="vt:oblob"/>
|
||||
<xsd:element ref="vt:empty"/>
|
||||
<xsd:element ref="vt:null"/>
|
||||
<xsd:element ref="vt:i1"/>
|
||||
<xsd:element ref="vt:i2"/>
|
||||
<xsd:element ref="vt:i4"/>
|
||||
<xsd:element ref="vt:i8"/>
|
||||
<xsd:element ref="vt:int"/>
|
||||
<xsd:element ref="vt:ui1"/>
|
||||
<xsd:element ref="vt:ui2"/>
|
||||
<xsd:element ref="vt:ui4"/>
|
||||
<xsd:element ref="vt:ui8"/>
|
||||
<xsd:element ref="vt:uint"/>
|
||||
<xsd:element ref="vt:r4"/>
|
||||
<xsd:element ref="vt:r8"/>
|
||||
<xsd:element ref="vt:decimal"/>
|
||||
<xsd:element ref="vt:lpstr"/>
|
||||
<xsd:element ref="vt:lpwstr"/>
|
||||
<xsd:element ref="vt:bstr"/>
|
||||
<xsd:element ref="vt:date"/>
|
||||
<xsd:element ref="vt:filetime"/>
|
||||
<xsd:element ref="vt:bool"/>
|
||||
<xsd:element ref="vt:cy"/>
|
||||
<xsd:element ref="vt:error"/>
|
||||
<xsd:element ref="vt:stream"/>
|
||||
<xsd:element ref="vt:ostream"/>
|
||||
<xsd:element ref="vt:storage"/>
|
||||
<xsd:element ref="vt:ostorage"/>
|
||||
<xsd:element ref="vt:vstream"/>
|
||||
<xsd:element ref="vt:clsid"/>
|
||||
</xsd:choice>
|
||||
<xsd:attribute name="fmtid" use="required" type="s:ST_Guid"/>
|
||||
<xsd:attribute name="pid" use="required" type="xsd:int"/>
|
||||
<xsd:attribute name="name" use="optional" type="xsd:string"/>
|
||||
<xsd:attribute name="linkTarget" use="optional" type="xsd:string"/>
|
||||
</xsd:complexType>
|
||||
</xsd:schema>
|
||||
@@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties"
|
||||
xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"
|
||||
targetNamespace="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties"
|
||||
elementFormDefault="qualified" blockDefault="#all">
|
||||
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"
|
||||
schemaLocation="shared-documentPropertiesVariantTypes.xsd"/>
|
||||
<xsd:element name="Properties" type="CT_Properties"/>
|
||||
<xsd:complexType name="CT_Properties">
|
||||
<xsd:all>
|
||||
<xsd:element name="Template" minOccurs="0" maxOccurs="1" type="xsd:string"/>
|
||||
<xsd:element name="Manager" minOccurs="0" maxOccurs="1" type="xsd:string"/>
|
||||
<xsd:element name="Company" minOccurs="0" maxOccurs="1" type="xsd:string"/>
|
||||
<xsd:element name="Pages" minOccurs="0" maxOccurs="1" type="xsd:int"/>
|
||||
<xsd:element name="Words" minOccurs="0" maxOccurs="1" type="xsd:int"/>
|
||||
<xsd:element name="Characters" minOccurs="0" maxOccurs="1" type="xsd:int"/>
|
||||
<xsd:element name="PresentationFormat" minOccurs="0" maxOccurs="1" type="xsd:string"/>
|
||||
<xsd:element name="Lines" minOccurs="0" maxOccurs="1" type="xsd:int"/>
|
||||
<xsd:element name="Paragraphs" minOccurs="0" maxOccurs="1" type="xsd:int"/>
|
||||
<xsd:element name="Slides" minOccurs="0" maxOccurs="1" type="xsd:int"/>
|
||||
<xsd:element name="Notes" minOccurs="0" maxOccurs="1" type="xsd:int"/>
|
||||
<xsd:element name="TotalTime" minOccurs="0" maxOccurs="1" type="xsd:int"/>
|
||||
<xsd:element name="HiddenSlides" minOccurs="0" maxOccurs="1" type="xsd:int"/>
|
||||
<xsd:element name="MMClips" minOccurs="0" maxOccurs="1" type="xsd:int"/>
|
||||
<xsd:element name="ScaleCrop" minOccurs="0" maxOccurs="1" type="xsd:boolean"/>
|
||||
<xsd:element name="HeadingPairs" minOccurs="0" maxOccurs="1" type="CT_VectorVariant"/>
|
||||
<xsd:element name="TitlesOfParts" minOccurs="0" maxOccurs="1" type="CT_VectorLpstr"/>
|
||||
<xsd:element name="LinksUpToDate" minOccurs="0" maxOccurs="1" type="xsd:boolean"/>
|
||||
<xsd:element name="CharactersWithSpaces" minOccurs="0" maxOccurs="1" type="xsd:int"/>
|
||||
<xsd:element name="SharedDoc" minOccurs="0" maxOccurs="1" type="xsd:boolean"/>
|
||||
<xsd:element name="HyperlinkBase" minOccurs="0" maxOccurs="1" type="xsd:string"/>
|
||||
<xsd:element name="HLinks" minOccurs="0" maxOccurs="1" type="CT_VectorVariant"/>
|
||||
<xsd:element name="HyperlinksChanged" minOccurs="0" maxOccurs="1" type="xsd:boolean"/>
|
||||
<xsd:element name="DigSig" minOccurs="0" maxOccurs="1" type="CT_DigSigBlob"/>
|
||||
<xsd:element name="Application" minOccurs="0" maxOccurs="1" type="xsd:string"/>
|
||||
<xsd:element name="AppVersion" minOccurs="0" maxOccurs="1" type="xsd:string"/>
|
||||
<xsd:element name="DocSecurity" minOccurs="0" maxOccurs="1" type="xsd:int"/>
|
||||
</xsd:all>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_VectorVariant">
|
||||
<xsd:sequence minOccurs="1" maxOccurs="1">
|
||||
<xsd:element ref="vt:vector"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_VectorLpstr">
|
||||
<xsd:sequence minOccurs="1" maxOccurs="1">
|
||||
<xsd:element ref="vt:vector"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_DigSigBlob">
|
||||
<xsd:sequence minOccurs="1" maxOccurs="1">
|
||||
<xsd:element ref="vt:blob"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
</xsd:schema>
|
||||
@@ -0,0 +1,195 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"
|
||||
xmlns:s="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
|
||||
targetNamespace="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"
|
||||
blockDefault="#all" elementFormDefault="qualified">
|
||||
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
|
||||
schemaLocation="shared-commonSimpleTypes.xsd"/>
|
||||
<xsd:simpleType name="ST_VectorBaseType">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="variant"/>
|
||||
<xsd:enumeration value="i1"/>
|
||||
<xsd:enumeration value="i2"/>
|
||||
<xsd:enumeration value="i4"/>
|
||||
<xsd:enumeration value="i8"/>
|
||||
<xsd:enumeration value="ui1"/>
|
||||
<xsd:enumeration value="ui2"/>
|
||||
<xsd:enumeration value="ui4"/>
|
||||
<xsd:enumeration value="ui8"/>
|
||||
<xsd:enumeration value="r4"/>
|
||||
<xsd:enumeration value="r8"/>
|
||||
<xsd:enumeration value="lpstr"/>
|
||||
<xsd:enumeration value="lpwstr"/>
|
||||
<xsd:enumeration value="bstr"/>
|
||||
<xsd:enumeration value="date"/>
|
||||
<xsd:enumeration value="filetime"/>
|
||||
<xsd:enumeration value="bool"/>
|
||||
<xsd:enumeration value="cy"/>
|
||||
<xsd:enumeration value="error"/>
|
||||
<xsd:enumeration value="clsid"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_ArrayBaseType">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="variant"/>
|
||||
<xsd:enumeration value="i1"/>
|
||||
<xsd:enumeration value="i2"/>
|
||||
<xsd:enumeration value="i4"/>
|
||||
<xsd:enumeration value="int"/>
|
||||
<xsd:enumeration value="ui1"/>
|
||||
<xsd:enumeration value="ui2"/>
|
||||
<xsd:enumeration value="ui4"/>
|
||||
<xsd:enumeration value="uint"/>
|
||||
<xsd:enumeration value="r4"/>
|
||||
<xsd:enumeration value="r8"/>
|
||||
<xsd:enumeration value="decimal"/>
|
||||
<xsd:enumeration value="bstr"/>
|
||||
<xsd:enumeration value="date"/>
|
||||
<xsd:enumeration value="bool"/>
|
||||
<xsd:enumeration value="cy"/>
|
||||
<xsd:enumeration value="error"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_Cy">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:pattern value="\s*[0-9]*\.[0-9]{4}\s*"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_Error">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:pattern value="\s*0x[0-9A-Za-z]{8}\s*"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_Empty"/>
|
||||
<xsd:complexType name="CT_Null"/>
|
||||
<xsd:complexType name="CT_Vector">
|
||||
<xsd:choice minOccurs="1" maxOccurs="unbounded">
|
||||
<xsd:element ref="variant"/>
|
||||
<xsd:element ref="i1"/>
|
||||
<xsd:element ref="i2"/>
|
||||
<xsd:element ref="i4"/>
|
||||
<xsd:element ref="i8"/>
|
||||
<xsd:element ref="ui1"/>
|
||||
<xsd:element ref="ui2"/>
|
||||
<xsd:element ref="ui4"/>
|
||||
<xsd:element ref="ui8"/>
|
||||
<xsd:element ref="r4"/>
|
||||
<xsd:element ref="r8"/>
|
||||
<xsd:element ref="lpstr"/>
|
||||
<xsd:element ref="lpwstr"/>
|
||||
<xsd:element ref="bstr"/>
|
||||
<xsd:element ref="date"/>
|
||||
<xsd:element ref="filetime"/>
|
||||
<xsd:element ref="bool"/>
|
||||
<xsd:element ref="cy"/>
|
||||
<xsd:element ref="error"/>
|
||||
<xsd:element ref="clsid"/>
|
||||
</xsd:choice>
|
||||
<xsd:attribute name="baseType" type="ST_VectorBaseType" use="required"/>
|
||||
<xsd:attribute name="size" type="xsd:unsignedInt" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Array">
|
||||
<xsd:choice minOccurs="1" maxOccurs="unbounded">
|
||||
<xsd:element ref="variant"/>
|
||||
<xsd:element ref="i1"/>
|
||||
<xsd:element ref="i2"/>
|
||||
<xsd:element ref="i4"/>
|
||||
<xsd:element ref="int"/>
|
||||
<xsd:element ref="ui1"/>
|
||||
<xsd:element ref="ui2"/>
|
||||
<xsd:element ref="ui4"/>
|
||||
<xsd:element ref="uint"/>
|
||||
<xsd:element ref="r4"/>
|
||||
<xsd:element ref="r8"/>
|
||||
<xsd:element ref="decimal"/>
|
||||
<xsd:element ref="bstr"/>
|
||||
<xsd:element ref="date"/>
|
||||
<xsd:element ref="bool"/>
|
||||
<xsd:element ref="error"/>
|
||||
<xsd:element ref="cy"/>
|
||||
</xsd:choice>
|
||||
<xsd:attribute name="lBounds" type="xsd:int" use="required"/>
|
||||
<xsd:attribute name="uBounds" type="xsd:int" use="required"/>
|
||||
<xsd:attribute name="baseType" type="ST_ArrayBaseType" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Variant">
|
||||
<xsd:choice minOccurs="1" maxOccurs="1">
|
||||
<xsd:element ref="variant"/>
|
||||
<xsd:element ref="vector"/>
|
||||
<xsd:element ref="array"/>
|
||||
<xsd:element ref="blob"/>
|
||||
<xsd:element ref="oblob"/>
|
||||
<xsd:element ref="empty"/>
|
||||
<xsd:element ref="null"/>
|
||||
<xsd:element ref="i1"/>
|
||||
<xsd:element ref="i2"/>
|
||||
<xsd:element ref="i4"/>
|
||||
<xsd:element ref="i8"/>
|
||||
<xsd:element ref="int"/>
|
||||
<xsd:element ref="ui1"/>
|
||||
<xsd:element ref="ui2"/>
|
||||
<xsd:element ref="ui4"/>
|
||||
<xsd:element ref="ui8"/>
|
||||
<xsd:element ref="uint"/>
|
||||
<xsd:element ref="r4"/>
|
||||
<xsd:element ref="r8"/>
|
||||
<xsd:element ref="decimal"/>
|
||||
<xsd:element ref="lpstr"/>
|
||||
<xsd:element ref="lpwstr"/>
|
||||
<xsd:element ref="bstr"/>
|
||||
<xsd:element ref="date"/>
|
||||
<xsd:element ref="filetime"/>
|
||||
<xsd:element ref="bool"/>
|
||||
<xsd:element ref="cy"/>
|
||||
<xsd:element ref="error"/>
|
||||
<xsd:element ref="stream"/>
|
||||
<xsd:element ref="ostream"/>
|
||||
<xsd:element ref="storage"/>
|
||||
<xsd:element ref="ostorage"/>
|
||||
<xsd:element ref="vstream"/>
|
||||
<xsd:element ref="clsid"/>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Vstream">
|
||||
<xsd:simpleContent>
|
||||
<xsd:extension base="xsd:base64Binary">
|
||||
<xsd:attribute name="version" type="s:ST_Guid"/>
|
||||
</xsd:extension>
|
||||
</xsd:simpleContent>
|
||||
</xsd:complexType>
|
||||
<xsd:element name="variant" type="CT_Variant"/>
|
||||
<xsd:element name="vector" type="CT_Vector"/>
|
||||
<xsd:element name="array" type="CT_Array"/>
|
||||
<xsd:element name="blob" type="xsd:base64Binary"/>
|
||||
<xsd:element name="oblob" type="xsd:base64Binary"/>
|
||||
<xsd:element name="empty" type="CT_Empty"/>
|
||||
<xsd:element name="null" type="CT_Null"/>
|
||||
<xsd:element name="i1" type="xsd:byte"/>
|
||||
<xsd:element name="i2" type="xsd:short"/>
|
||||
<xsd:element name="i4" type="xsd:int"/>
|
||||
<xsd:element name="i8" type="xsd:long"/>
|
||||
<xsd:element name="int" type="xsd:int"/>
|
||||
<xsd:element name="ui1" type="xsd:unsignedByte"/>
|
||||
<xsd:element name="ui2" type="xsd:unsignedShort"/>
|
||||
<xsd:element name="ui4" type="xsd:unsignedInt"/>
|
||||
<xsd:element name="ui8" type="xsd:unsignedLong"/>
|
||||
<xsd:element name="uint" type="xsd:unsignedInt"/>
|
||||
<xsd:element name="r4" type="xsd:float"/>
|
||||
<xsd:element name="r8" type="xsd:double"/>
|
||||
<xsd:element name="decimal" type="xsd:decimal"/>
|
||||
<xsd:element name="lpstr" type="xsd:string"/>
|
||||
<xsd:element name="lpwstr" type="xsd:string"/>
|
||||
<xsd:element name="bstr" type="xsd:string"/>
|
||||
<xsd:element name="date" type="xsd:dateTime"/>
|
||||
<xsd:element name="filetime" type="xsd:dateTime"/>
|
||||
<xsd:element name="bool" type="xsd:boolean"/>
|
||||
<xsd:element name="cy" type="ST_Cy"/>
|
||||
<xsd:element name="error" type="ST_Error"/>
|
||||
<xsd:element name="stream" type="xsd:base64Binary"/>
|
||||
<xsd:element name="ostream" type="xsd:base64Binary"/>
|
||||
<xsd:element name="storage" type="xsd:base64Binary"/>
|
||||
<xsd:element name="ostorage" type="xsd:base64Binary"/>
|
||||
<xsd:element name="vstream" type="CT_Vstream"/>
|
||||
<xsd:element name="clsid" type="s:ST_Guid"/>
|
||||
</xsd:schema>
|
||||
582
skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd
Executable file
582
skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd
Executable file
@@ -0,0 +1,582 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns="http://schemas.openxmlformats.org/officeDocument/2006/math"
|
||||
xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math"
|
||||
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
|
||||
xmlns:s="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
|
||||
elementFormDefault="qualified" attributeFormDefault="qualified" blockDefault="#all"
|
||||
targetNamespace="http://schemas.openxmlformats.org/officeDocument/2006/math">
|
||||
<xsd:import namespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
|
||||
schemaLocation="wml.xsd"/>
|
||||
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
|
||||
schemaLocation="shared-commonSimpleTypes.xsd"/>
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" schemaLocation="xml.xsd"/>
|
||||
<xsd:simpleType name="ST_Integer255">
|
||||
<xsd:restriction base="xsd:integer">
|
||||
<xsd:minInclusive value="1"/>
|
||||
<xsd:maxInclusive value="255"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_Integer255">
|
||||
<xsd:attribute name="val" type="ST_Integer255" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_Integer2">
|
||||
<xsd:restriction base="xsd:integer">
|
||||
<xsd:minInclusive value="-2"/>
|
||||
<xsd:maxInclusive value="2"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_Integer2">
|
||||
<xsd:attribute name="val" type="ST_Integer2" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_SpacingRule">
|
||||
<xsd:restriction base="xsd:integer">
|
||||
<xsd:minInclusive value="0"/>
|
||||
<xsd:maxInclusive value="4"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_SpacingRule">
|
||||
<xsd:attribute name="val" type="ST_SpacingRule" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_UnSignedInteger">
|
||||
<xsd:restriction base="xsd:unsignedInt"/>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_UnSignedInteger">
|
||||
<xsd:attribute name="val" type="ST_UnSignedInteger" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_Char">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:maxLength value="1"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_Char">
|
||||
<xsd:attribute name="val" type="ST_Char" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_OnOff">
|
||||
<xsd:attribute name="val" type="s:ST_OnOff"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_String">
|
||||
<xsd:attribute name="val" type="s:ST_String"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_XAlign">
|
||||
<xsd:attribute name="val" type="s:ST_XAlign" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_YAlign">
|
||||
<xsd:attribute name="val" type="s:ST_YAlign" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_Shp">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="centered"/>
|
||||
<xsd:enumeration value="match"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_Shp">
|
||||
<xsd:attribute name="val" type="ST_Shp" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_FType">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="bar"/>
|
||||
<xsd:enumeration value="skw"/>
|
||||
<xsd:enumeration value="lin"/>
|
||||
<xsd:enumeration value="noBar"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_FType">
|
||||
<xsd:attribute name="val" type="ST_FType" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_LimLoc">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="undOvr"/>
|
||||
<xsd:enumeration value="subSup"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_LimLoc">
|
||||
<xsd:attribute name="val" type="ST_LimLoc" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_TopBot">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="top"/>
|
||||
<xsd:enumeration value="bot"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_TopBot">
|
||||
<xsd:attribute name="val" type="ST_TopBot" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_Script">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="roman"/>
|
||||
<xsd:enumeration value="script"/>
|
||||
<xsd:enumeration value="fraktur"/>
|
||||
<xsd:enumeration value="double-struck"/>
|
||||
<xsd:enumeration value="sans-serif"/>
|
||||
<xsd:enumeration value="monospace"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_Script">
|
||||
<xsd:attribute name="val" type="ST_Script"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_Style">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="p"/>
|
||||
<xsd:enumeration value="b"/>
|
||||
<xsd:enumeration value="i"/>
|
||||
<xsd:enumeration value="bi"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_Style">
|
||||
<xsd:attribute name="val" type="ST_Style"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_ManualBreak">
|
||||
<xsd:attribute name="alnAt" type="ST_Integer255"/>
|
||||
</xsd:complexType>
|
||||
<xsd:group name="EG_ScriptStyle">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="scr" minOccurs="0" type="CT_Script"/>
|
||||
<xsd:element name="sty" minOccurs="0" type="CT_Style"/>
|
||||
</xsd:sequence>
|
||||
</xsd:group>
|
||||
<xsd:complexType name="CT_RPR">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="lit" minOccurs="0" type="CT_OnOff"/>
|
||||
<xsd:choice>
|
||||
<xsd:element name="nor" minOccurs="0" type="CT_OnOff"/>
|
||||
<xsd:sequence>
|
||||
<xsd:group ref="EG_ScriptStyle"/>
|
||||
</xsd:sequence>
|
||||
</xsd:choice>
|
||||
<xsd:element name="brk" minOccurs="0" type="CT_ManualBreak"/>
|
||||
<xsd:element name="aln" minOccurs="0" type="CT_OnOff"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Text">
|
||||
<xsd:simpleContent>
|
||||
<xsd:extension base="s:ST_String">
|
||||
<xsd:attribute ref="xml:space" use="optional"/>
|
||||
</xsd:extension>
|
||||
</xsd:simpleContent>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_R">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="rPr" type="CT_RPR" minOccurs="0"/>
|
||||
<xsd:group ref="w:EG_RPr" minOccurs="0"/>
|
||||
<xsd:choice minOccurs="0" maxOccurs="unbounded">
|
||||
<xsd:group ref="w:EG_RunInnerContent"/>
|
||||
<xsd:element name="t" type="CT_Text" minOccurs="0"/>
|
||||
</xsd:choice>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_CtrlPr">
|
||||
<xsd:sequence>
|
||||
<xsd:group ref="w:EG_RPrMath" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_AccPr">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="chr" type="CT_Char" minOccurs="0"/>
|
||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Acc">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="accPr" type="CT_AccPr" minOccurs="0"/>
|
||||
<xsd:element name="e" type="CT_OMathArg"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_BarPr">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="pos" type="CT_TopBot" minOccurs="0"/>
|
||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Bar">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="barPr" type="CT_BarPr" minOccurs="0"/>
|
||||
<xsd:element name="e" type="CT_OMathArg"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_BoxPr">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="opEmu" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="noBreak" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="diff" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="brk" type="CT_ManualBreak" minOccurs="0"/>
|
||||
<xsd:element name="aln" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Box">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="boxPr" type="CT_BoxPr" minOccurs="0"/>
|
||||
<xsd:element name="e" type="CT_OMathArg"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_BorderBoxPr">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="hideTop" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="hideBot" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="hideLeft" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="hideRight" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="strikeH" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="strikeV" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="strikeBLTR" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="strikeTLBR" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_BorderBox">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="borderBoxPr" type="CT_BorderBoxPr" minOccurs="0"/>
|
||||
<xsd:element name="e" type="CT_OMathArg"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_DPr">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="begChr" type="CT_Char" minOccurs="0"/>
|
||||
<xsd:element name="sepChr" type="CT_Char" minOccurs="0"/>
|
||||
<xsd:element name="endChr" type="CT_Char" minOccurs="0"/>
|
||||
<xsd:element name="grow" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="shp" type="CT_Shp" minOccurs="0"/>
|
||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_D">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="dPr" type="CT_DPr" minOccurs="0"/>
|
||||
<xsd:element name="e" type="CT_OMathArg" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_EqArrPr">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="baseJc" type="CT_YAlign" minOccurs="0"/>
|
||||
<xsd:element name="maxDist" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="objDist" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="rSpRule" type="CT_SpacingRule" minOccurs="0"/>
|
||||
<xsd:element name="rSp" type="CT_UnSignedInteger" minOccurs="0"/>
|
||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_EqArr">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="eqArrPr" type="CT_EqArrPr" minOccurs="0"/>
|
||||
<xsd:element name="e" type="CT_OMathArg" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_FPr">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="type" type="CT_FType" minOccurs="0"/>
|
||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_F">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="fPr" type="CT_FPr" minOccurs="0"/>
|
||||
<xsd:element name="num" type="CT_OMathArg"/>
|
||||
<xsd:element name="den" type="CT_OMathArg"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_FuncPr">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Func">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="funcPr" type="CT_FuncPr" minOccurs="0"/>
|
||||
<xsd:element name="fName" type="CT_OMathArg"/>
|
||||
<xsd:element name="e" type="CT_OMathArg"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_GroupChrPr">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="chr" type="CT_Char" minOccurs="0"/>
|
||||
<xsd:element name="pos" type="CT_TopBot" minOccurs="0"/>
|
||||
<xsd:element name="vertJc" type="CT_TopBot" minOccurs="0"/>
|
||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_GroupChr">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="groupChrPr" type="CT_GroupChrPr" minOccurs="0"/>
|
||||
<xsd:element name="e" type="CT_OMathArg"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_LimLowPr">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_LimLow">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="limLowPr" type="CT_LimLowPr" minOccurs="0"/>
|
||||
<xsd:element name="e" type="CT_OMathArg"/>
|
||||
<xsd:element name="lim" type="CT_OMathArg"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_LimUppPr">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_LimUpp">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="limUppPr" type="CT_LimUppPr" minOccurs="0"/>
|
||||
<xsd:element name="e" type="CT_OMathArg"/>
|
||||
<xsd:element name="lim" type="CT_OMathArg"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_MCPr">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="count" type="CT_Integer255" minOccurs="0"/>
|
||||
<xsd:element name="mcJc" type="CT_XAlign" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_MC">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="mcPr" type="CT_MCPr" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_MCS">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="mc" type="CT_MC" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_MPr">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="baseJc" type="CT_YAlign" minOccurs="0"/>
|
||||
<xsd:element name="plcHide" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="rSpRule" type="CT_SpacingRule" minOccurs="0"/>
|
||||
<xsd:element name="cGpRule" type="CT_SpacingRule" minOccurs="0"/>
|
||||
<xsd:element name="rSp" type="CT_UnSignedInteger" minOccurs="0"/>
|
||||
<xsd:element name="cSp" type="CT_UnSignedInteger" minOccurs="0"/>
|
||||
<xsd:element name="cGp" type="CT_UnSignedInteger" minOccurs="0"/>
|
||||
<xsd:element name="mcs" type="CT_MCS" minOccurs="0"/>
|
||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_MR">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="e" type="CT_OMathArg" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_M">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="mPr" type="CT_MPr" minOccurs="0"/>
|
||||
<xsd:element name="mr" type="CT_MR" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_NaryPr">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="chr" type="CT_Char" minOccurs="0"/>
|
||||
<xsd:element name="limLoc" type="CT_LimLoc" minOccurs="0"/>
|
||||
<xsd:element name="grow" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="subHide" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="supHide" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Nary">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="naryPr" type="CT_NaryPr" minOccurs="0"/>
|
||||
<xsd:element name="sub" type="CT_OMathArg"/>
|
||||
<xsd:element name="sup" type="CT_OMathArg"/>
|
||||
<xsd:element name="e" type="CT_OMathArg"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_PhantPr">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="show" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="zeroWid" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="zeroAsc" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="zeroDesc" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="transp" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Phant">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="phantPr" type="CT_PhantPr" minOccurs="0"/>
|
||||
<xsd:element name="e" type="CT_OMathArg"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_RadPr">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="degHide" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Rad">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="radPr" type="CT_RadPr" minOccurs="0"/>
|
||||
<xsd:element name="deg" type="CT_OMathArg"/>
|
||||
<xsd:element name="e" type="CT_OMathArg"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_SPrePr">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_SPre">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="sPrePr" type="CT_SPrePr" minOccurs="0"/>
|
||||
<xsd:element name="sub" type="CT_OMathArg"/>
|
||||
<xsd:element name="sup" type="CT_OMathArg"/>
|
||||
<xsd:element name="e" type="CT_OMathArg"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_SSubPr">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_SSub">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="sSubPr" type="CT_SSubPr" minOccurs="0"/>
|
||||
<xsd:element name="e" type="CT_OMathArg"/>
|
||||
<xsd:element name="sub" type="CT_OMathArg"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_SSubSupPr">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="alnScr" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_SSubSup">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="sSubSupPr" type="CT_SSubSupPr" minOccurs="0"/>
|
||||
<xsd:element name="e" type="CT_OMathArg"/>
|
||||
<xsd:element name="sub" type="CT_OMathArg"/>
|
||||
<xsd:element name="sup" type="CT_OMathArg"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_SSupPr">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_SSup">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="sSupPr" type="CT_SSupPr" minOccurs="0"/>
|
||||
<xsd:element name="e" type="CT_OMathArg"/>
|
||||
<xsd:element name="sup" type="CT_OMathArg"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:group name="EG_OMathMathElements">
|
||||
<xsd:choice>
|
||||
<xsd:element name="acc" type="CT_Acc"/>
|
||||
<xsd:element name="bar" type="CT_Bar"/>
|
||||
<xsd:element name="box" type="CT_Box"/>
|
||||
<xsd:element name="borderBox" type="CT_BorderBox"/>
|
||||
<xsd:element name="d" type="CT_D"/>
|
||||
<xsd:element name="eqArr" type="CT_EqArr"/>
|
||||
<xsd:element name="f" type="CT_F"/>
|
||||
<xsd:element name="func" type="CT_Func"/>
|
||||
<xsd:element name="groupChr" type="CT_GroupChr"/>
|
||||
<xsd:element name="limLow" type="CT_LimLow"/>
|
||||
<xsd:element name="limUpp" type="CT_LimUpp"/>
|
||||
<xsd:element name="m" type="CT_M"/>
|
||||
<xsd:element name="nary" type="CT_Nary"/>
|
||||
<xsd:element name="phant" type="CT_Phant"/>
|
||||
<xsd:element name="rad" type="CT_Rad"/>
|
||||
<xsd:element name="sPre" type="CT_SPre"/>
|
||||
<xsd:element name="sSub" type="CT_SSub"/>
|
||||
<xsd:element name="sSubSup" type="CT_SSubSup"/>
|
||||
<xsd:element name="sSup" type="CT_SSup"/>
|
||||
<xsd:element name="r" type="CT_R"/>
|
||||
</xsd:choice>
|
||||
</xsd:group>
|
||||
<xsd:group name="EG_OMathElements">
|
||||
<xsd:choice>
|
||||
<xsd:group ref="EG_OMathMathElements"/>
|
||||
<xsd:group ref="w:EG_PContentMath"/>
|
||||
</xsd:choice>
|
||||
</xsd:group>
|
||||
<xsd:complexType name="CT_OMathArgPr">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="argSz" type="CT_Integer2" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_OMathArg">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="argPr" type="CT_OMathArgPr" minOccurs="0"/>
|
||||
<xsd:group ref="EG_OMathElements" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_Jc">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="left"/>
|
||||
<xsd:enumeration value="right"/>
|
||||
<xsd:enumeration value="center"/>
|
||||
<xsd:enumeration value="centerGroup"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_OMathJc">
|
||||
<xsd:attribute name="val" type="ST_Jc"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_OMathParaPr">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="jc" type="CT_OMathJc" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_TwipsMeasure">
|
||||
<xsd:attribute name="val" type="s:ST_TwipsMeasure" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_BreakBin">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="before"/>
|
||||
<xsd:enumeration value="after"/>
|
||||
<xsd:enumeration value="repeat"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_BreakBin">
|
||||
<xsd:attribute name="val" type="ST_BreakBin"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_BreakBinSub">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="--"/>
|
||||
<xsd:enumeration value="-+"/>
|
||||
<xsd:enumeration value="+-"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_BreakBinSub">
|
||||
<xsd:attribute name="val" type="ST_BreakBinSub"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_MathPr">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="mathFont" type="CT_String" minOccurs="0"/>
|
||||
<xsd:element name="brkBin" type="CT_BreakBin" minOccurs="0"/>
|
||||
<xsd:element name="brkBinSub" type="CT_BreakBinSub" minOccurs="0"/>
|
||||
<xsd:element name="smallFrac" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="dispDef" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="lMargin" type="CT_TwipsMeasure" minOccurs="0"/>
|
||||
<xsd:element name="rMargin" type="CT_TwipsMeasure" minOccurs="0"/>
|
||||
<xsd:element name="defJc" type="CT_OMathJc" minOccurs="0"/>
|
||||
<xsd:element name="preSp" type="CT_TwipsMeasure" minOccurs="0"/>
|
||||
<xsd:element name="postSp" type="CT_TwipsMeasure" minOccurs="0"/>
|
||||
<xsd:element name="interSp" type="CT_TwipsMeasure" minOccurs="0"/>
|
||||
<xsd:element name="intraSp" type="CT_TwipsMeasure" minOccurs="0"/>
|
||||
<xsd:choice minOccurs="0">
|
||||
<xsd:element name="wrapIndent" type="CT_TwipsMeasure"/>
|
||||
<xsd:element name="wrapRight" type="CT_OnOff"/>
|
||||
</xsd:choice>
|
||||
<xsd:element name="intLim" type="CT_LimLoc" minOccurs="0"/>
|
||||
<xsd:element name="naryLim" type="CT_LimLoc" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:element name="mathPr" type="CT_MathPr"/>
|
||||
<xsd:complexType name="CT_OMathPara">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="oMathParaPr" type="CT_OMathParaPr" minOccurs="0"/>
|
||||
<xsd:element name="oMath" type="CT_OMath" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_OMath">
|
||||
<xsd:sequence>
|
||||
<xsd:group ref="EG_OMathElements" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:element name="oMathPara" type="CT_OMathPara"/>
|
||||
<xsd:element name="oMath" type="CT_OMath"/>
|
||||
</xsd:schema>
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
|
||||
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
|
||||
elementFormDefault="qualified"
|
||||
targetNamespace="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
|
||||
blockDefault="#all">
|
||||
<xsd:simpleType name="ST_RelationshipId">
|
||||
<xsd:restriction base="xsd:string"/>
|
||||
</xsd:simpleType>
|
||||
<xsd:attribute name="id" type="ST_RelationshipId"/>
|
||||
<xsd:attribute name="embed" type="ST_RelationshipId"/>
|
||||
<xsd:attribute name="link" type="ST_RelationshipId"/>
|
||||
<xsd:attribute name="dm" type="ST_RelationshipId" default=""/>
|
||||
<xsd:attribute name="lo" type="ST_RelationshipId" default=""/>
|
||||
<xsd:attribute name="qs" type="ST_RelationshipId" default=""/>
|
||||
<xsd:attribute name="cs" type="ST_RelationshipId" default=""/>
|
||||
<xsd:attribute name="blip" type="ST_RelationshipId" default=""/>
|
||||
<xsd:attribute name="pict" type="ST_RelationshipId"/>
|
||||
<xsd:attribute name="href" type="ST_RelationshipId"/>
|
||||
<xsd:attribute name="topLeft" type="ST_RelationshipId"/>
|
||||
<xsd:attribute name="topRight" type="ST_RelationshipId"/>
|
||||
<xsd:attribute name="bottomLeft" type="ST_RelationshipId"/>
|
||||
<xsd:attribute name="bottomRight" type="ST_RelationshipId"/>
|
||||
</xsd:schema>
|
||||
4439
skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd
Executable file
4439
skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd
Executable file
File diff suppressed because it is too large
Load Diff
570
skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd
Executable file
570
skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd
Executable file
@@ -0,0 +1,570 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="urn:schemas-microsoft-com:vml"
|
||||
xmlns:pvml="urn:schemas-microsoft-com:office:powerpoint"
|
||||
xmlns:o="urn:schemas-microsoft-com:office:office"
|
||||
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
|
||||
xmlns:w10="urn:schemas-microsoft-com:office:word"
|
||||
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
|
||||
xmlns:x="urn:schemas-microsoft-com:office:excel"
|
||||
xmlns:s="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
|
||||
targetNamespace="urn:schemas-microsoft-com:vml" elementFormDefault="qualified"
|
||||
attributeFormDefault="unqualified">
|
||||
<xsd:import namespace="urn:schemas-microsoft-com:office:office"
|
||||
schemaLocation="vml-officeDrawing.xsd"/>
|
||||
<xsd:import namespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
|
||||
schemaLocation="wml.xsd"/>
|
||||
<xsd:import namespace="urn:schemas-microsoft-com:office:word"
|
||||
schemaLocation="vml-wordprocessingDrawing.xsd"/>
|
||||
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
|
||||
schemaLocation="shared-relationshipReference.xsd"/>
|
||||
<xsd:import namespace="urn:schemas-microsoft-com:office:excel"
|
||||
schemaLocation="vml-spreadsheetDrawing.xsd"/>
|
||||
<xsd:import namespace="urn:schemas-microsoft-com:office:powerpoint"
|
||||
schemaLocation="vml-presentationDrawing.xsd"/>
|
||||
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
|
||||
schemaLocation="shared-commonSimpleTypes.xsd"/>
|
||||
<xsd:attributeGroup name="AG_Id">
|
||||
<xsd:attribute name="id" type="xsd:string" use="optional"/>
|
||||
</xsd:attributeGroup>
|
||||
<xsd:attributeGroup name="AG_Style">
|
||||
<xsd:attribute name="style" type="xsd:string" use="optional"/>
|
||||
</xsd:attributeGroup>
|
||||
<xsd:attributeGroup name="AG_Type">
|
||||
<xsd:attribute name="type" type="xsd:string" use="optional"/>
|
||||
</xsd:attributeGroup>
|
||||
<xsd:attributeGroup name="AG_Adj">
|
||||
<xsd:attribute name="adj" type="xsd:string" use="optional"/>
|
||||
</xsd:attributeGroup>
|
||||
<xsd:attributeGroup name="AG_Path">
|
||||
<xsd:attribute name="path" type="xsd:string" use="optional"/>
|
||||
</xsd:attributeGroup>
|
||||
<xsd:attributeGroup name="AG_Fill">
|
||||
<xsd:attribute name="filled" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="fillcolor" type="s:ST_ColorType" use="optional"/>
|
||||
</xsd:attributeGroup>
|
||||
<xsd:attributeGroup name="AG_Chromakey">
|
||||
<xsd:attribute name="chromakey" type="s:ST_ColorType" use="optional"/>
|
||||
</xsd:attributeGroup>
|
||||
<xsd:attributeGroup name="AG_Ext">
|
||||
<xsd:attribute name="ext" form="qualified" type="ST_Ext"/>
|
||||
</xsd:attributeGroup>
|
||||
<xsd:attributeGroup name="AG_CoreAttributes">
|
||||
<xsd:attributeGroup ref="AG_Id"/>
|
||||
<xsd:attributeGroup ref="AG_Style"/>
|
||||
<xsd:attribute name="href" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="target" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="class" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="title" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="alt" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="coordsize" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="coordorigin" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="wrapcoords" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="print" type="s:ST_TrueFalse" use="optional"/>
|
||||
</xsd:attributeGroup>
|
||||
<xsd:attributeGroup name="AG_ShapeAttributes">
|
||||
<xsd:attributeGroup ref="AG_Chromakey"/>
|
||||
<xsd:attributeGroup ref="AG_Fill"/>
|
||||
<xsd:attribute name="opacity" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="stroked" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="strokecolor" type="s:ST_ColorType" use="optional"/>
|
||||
<xsd:attribute name="strokeweight" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="insetpen" type="s:ST_TrueFalse" use="optional"/>
|
||||
</xsd:attributeGroup>
|
||||
<xsd:attributeGroup name="AG_OfficeCoreAttributes">
|
||||
<xsd:attribute ref="o:spid"/>
|
||||
<xsd:attribute ref="o:oned"/>
|
||||
<xsd:attribute ref="o:regroupid"/>
|
||||
<xsd:attribute ref="o:doubleclicknotify"/>
|
||||
<xsd:attribute ref="o:button"/>
|
||||
<xsd:attribute ref="o:userhidden"/>
|
||||
<xsd:attribute ref="o:bullet"/>
|
||||
<xsd:attribute ref="o:hr"/>
|
||||
<xsd:attribute ref="o:hrstd"/>
|
||||
<xsd:attribute ref="o:hrnoshade"/>
|
||||
<xsd:attribute ref="o:hrpct"/>
|
||||
<xsd:attribute ref="o:hralign"/>
|
||||
<xsd:attribute ref="o:allowincell"/>
|
||||
<xsd:attribute ref="o:allowoverlap"/>
|
||||
<xsd:attribute ref="o:userdrawn"/>
|
||||
<xsd:attribute ref="o:bordertopcolor"/>
|
||||
<xsd:attribute ref="o:borderleftcolor"/>
|
||||
<xsd:attribute ref="o:borderbottomcolor"/>
|
||||
<xsd:attribute ref="o:borderrightcolor"/>
|
||||
<xsd:attribute ref="o:dgmlayout"/>
|
||||
<xsd:attribute ref="o:dgmnodekind"/>
|
||||
<xsd:attribute ref="o:dgmlayoutmru"/>
|
||||
<xsd:attribute ref="o:insetmode"/>
|
||||
</xsd:attributeGroup>
|
||||
<xsd:attributeGroup name="AG_OfficeShapeAttributes">
|
||||
<xsd:attribute ref="o:spt"/>
|
||||
<xsd:attribute ref="o:connectortype"/>
|
||||
<xsd:attribute ref="o:bwmode"/>
|
||||
<xsd:attribute ref="o:bwpure"/>
|
||||
<xsd:attribute ref="o:bwnormal"/>
|
||||
<xsd:attribute ref="o:forcedash"/>
|
||||
<xsd:attribute ref="o:oleicon"/>
|
||||
<xsd:attribute ref="o:ole"/>
|
||||
<xsd:attribute ref="o:preferrelative"/>
|
||||
<xsd:attribute ref="o:cliptowrap"/>
|
||||
<xsd:attribute ref="o:clip"/>
|
||||
</xsd:attributeGroup>
|
||||
<xsd:attributeGroup name="AG_AllCoreAttributes">
|
||||
<xsd:attributeGroup ref="AG_CoreAttributes"/>
|
||||
<xsd:attributeGroup ref="AG_OfficeCoreAttributes"/>
|
||||
</xsd:attributeGroup>
|
||||
<xsd:attributeGroup name="AG_AllShapeAttributes">
|
||||
<xsd:attributeGroup ref="AG_ShapeAttributes"/>
|
||||
<xsd:attributeGroup ref="AG_OfficeShapeAttributes"/>
|
||||
</xsd:attributeGroup>
|
||||
<xsd:attributeGroup name="AG_ImageAttributes">
|
||||
<xsd:attribute name="src" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="cropleft" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="croptop" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="cropright" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="cropbottom" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="gain" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="blacklevel" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="gamma" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="grayscale" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="bilevel" type="s:ST_TrueFalse" use="optional"/>
|
||||
</xsd:attributeGroup>
|
||||
<xsd:attributeGroup name="AG_StrokeAttributes">
|
||||
<xsd:attribute name="on" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="weight" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="color" type="s:ST_ColorType" use="optional"/>
|
||||
<xsd:attribute name="opacity" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="linestyle" type="ST_StrokeLineStyle" use="optional"/>
|
||||
<xsd:attribute name="miterlimit" type="xsd:decimal" use="optional"/>
|
||||
<xsd:attribute name="joinstyle" type="ST_StrokeJoinStyle" use="optional"/>
|
||||
<xsd:attribute name="endcap" type="ST_StrokeEndCap" use="optional"/>
|
||||
<xsd:attribute name="dashstyle" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="filltype" type="ST_FillType" use="optional"/>
|
||||
<xsd:attribute name="src" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="imageaspect" type="ST_ImageAspect" use="optional"/>
|
||||
<xsd:attribute name="imagesize" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="imagealignshape" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="color2" type="s:ST_ColorType" use="optional"/>
|
||||
<xsd:attribute name="startarrow" type="ST_StrokeArrowType" use="optional"/>
|
||||
<xsd:attribute name="startarrowwidth" type="ST_StrokeArrowWidth" use="optional"/>
|
||||
<xsd:attribute name="startarrowlength" type="ST_StrokeArrowLength" use="optional"/>
|
||||
<xsd:attribute name="endarrow" type="ST_StrokeArrowType" use="optional"/>
|
||||
<xsd:attribute name="endarrowwidth" type="ST_StrokeArrowWidth" use="optional"/>
|
||||
<xsd:attribute name="endarrowlength" type="ST_StrokeArrowLength" use="optional"/>
|
||||
<xsd:attribute ref="o:href"/>
|
||||
<xsd:attribute ref="o:althref"/>
|
||||
<xsd:attribute ref="o:title"/>
|
||||
<xsd:attribute ref="o:forcedash"/>
|
||||
<xsd:attribute ref="r:id" use="optional"/>
|
||||
<xsd:attribute name="insetpen" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute ref="o:relid"/>
|
||||
</xsd:attributeGroup>
|
||||
<xsd:group name="EG_ShapeElements">
|
||||
<xsd:choice>
|
||||
<xsd:element ref="path"/>
|
||||
<xsd:element ref="formulas"/>
|
||||
<xsd:element ref="handles"/>
|
||||
<xsd:element ref="fill"/>
|
||||
<xsd:element ref="stroke"/>
|
||||
<xsd:element ref="shadow"/>
|
||||
<xsd:element ref="textbox"/>
|
||||
<xsd:element ref="textpath"/>
|
||||
<xsd:element ref="imagedata"/>
|
||||
<xsd:element ref="o:skew"/>
|
||||
<xsd:element ref="o:extrusion"/>
|
||||
<xsd:element ref="o:callout"/>
|
||||
<xsd:element ref="o:lock"/>
|
||||
<xsd:element ref="o:clippath"/>
|
||||
<xsd:element ref="o:signatureline"/>
|
||||
<xsd:element ref="w10:wrap"/>
|
||||
<xsd:element ref="w10:anchorlock"/>
|
||||
<xsd:element ref="w10:bordertop"/>
|
||||
<xsd:element ref="w10:borderbottom"/>
|
||||
<xsd:element ref="w10:borderleft"/>
|
||||
<xsd:element ref="w10:borderright"/>
|
||||
<xsd:element ref="x:ClientData" minOccurs="0"/>
|
||||
<xsd:element ref="pvml:textdata" minOccurs="0"/>
|
||||
</xsd:choice>
|
||||
</xsd:group>
|
||||
<xsd:element name="shape" type="CT_Shape"/>
|
||||
<xsd:element name="shapetype" type="CT_Shapetype"/>
|
||||
<xsd:element name="group" type="CT_Group"/>
|
||||
<xsd:element name="background" type="CT_Background"/>
|
||||
<xsd:complexType name="CT_Shape">
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:group ref="EG_ShapeElements"/>
|
||||
<xsd:element ref="o:ink"/>
|
||||
<xsd:element ref="pvml:iscomment"/>
|
||||
<xsd:element ref="o:equationxml"/>
|
||||
</xsd:choice>
|
||||
<xsd:attributeGroup ref="AG_AllCoreAttributes"/>
|
||||
<xsd:attributeGroup ref="AG_AllShapeAttributes"/>
|
||||
<xsd:attributeGroup ref="AG_Type"/>
|
||||
<xsd:attributeGroup ref="AG_Adj"/>
|
||||
<xsd:attributeGroup ref="AG_Path"/>
|
||||
<xsd:attribute ref="o:gfxdata"/>
|
||||
<xsd:attribute name="equationxml" type="xsd:string" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Shapetype">
|
||||
<xsd:sequence>
|
||||
<xsd:group ref="EG_ShapeElements" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xsd:element ref="o:complex" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attributeGroup ref="AG_AllCoreAttributes"/>
|
||||
<xsd:attributeGroup ref="AG_AllShapeAttributes"/>
|
||||
<xsd:attributeGroup ref="AG_Adj"/>
|
||||
<xsd:attributeGroup ref="AG_Path"/>
|
||||
<xsd:attribute ref="o:master"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Group">
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:group ref="EG_ShapeElements"/>
|
||||
<xsd:element ref="group"/>
|
||||
<xsd:element ref="shape"/>
|
||||
<xsd:element ref="shapetype"/>
|
||||
<xsd:element ref="arc"/>
|
||||
<xsd:element ref="curve"/>
|
||||
<xsd:element ref="image"/>
|
||||
<xsd:element ref="line"/>
|
||||
<xsd:element ref="oval"/>
|
||||
<xsd:element ref="polyline"/>
|
||||
<xsd:element ref="rect"/>
|
||||
<xsd:element ref="roundrect"/>
|
||||
<xsd:element ref="o:diagram"/>
|
||||
</xsd:choice>
|
||||
<xsd:attributeGroup ref="AG_AllCoreAttributes"/>
|
||||
<xsd:attributeGroup ref="AG_Fill"/>
|
||||
<xsd:attribute name="editas" type="ST_EditAs" use="optional"/>
|
||||
<xsd:attribute ref="o:tableproperties"/>
|
||||
<xsd:attribute ref="o:tablelimits"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Background">
|
||||
<xsd:sequence>
|
||||
<xsd:element ref="fill" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attributeGroup ref="AG_Id"/>
|
||||
<xsd:attributeGroup ref="AG_Fill"/>
|
||||
<xsd:attribute ref="o:bwmode"/>
|
||||
<xsd:attribute ref="o:bwpure"/>
|
||||
<xsd:attribute ref="o:bwnormal"/>
|
||||
<xsd:attribute ref="o:targetscreensize"/>
|
||||
</xsd:complexType>
|
||||
<xsd:element name="fill" type="CT_Fill"/>
|
||||
<xsd:element name="formulas" type="CT_Formulas"/>
|
||||
<xsd:element name="handles" type="CT_Handles"/>
|
||||
<xsd:element name="imagedata" type="CT_ImageData"/>
|
||||
<xsd:element name="path" type="CT_Path"/>
|
||||
<xsd:element name="textbox" type="CT_Textbox"/>
|
||||
<xsd:element name="shadow" type="CT_Shadow"/>
|
||||
<xsd:element name="stroke" type="CT_Stroke"/>
|
||||
<xsd:element name="textpath" type="CT_TextPath"/>
|
||||
<xsd:complexType name="CT_Fill">
|
||||
<xsd:sequence>
|
||||
<xsd:element ref="o:fill" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attributeGroup ref="AG_Id"/>
|
||||
<xsd:attribute name="type" type="ST_FillType" use="optional"/>
|
||||
<xsd:attribute name="on" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="color" type="s:ST_ColorType" use="optional"/>
|
||||
<xsd:attribute name="opacity" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="color2" type="s:ST_ColorType" use="optional"/>
|
||||
<xsd:attribute name="src" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute ref="o:href"/>
|
||||
<xsd:attribute ref="o:althref"/>
|
||||
<xsd:attribute name="size" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="origin" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="position" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="aspect" type="ST_ImageAspect" use="optional"/>
|
||||
<xsd:attribute name="colors" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="angle" type="xsd:decimal" use="optional"/>
|
||||
<xsd:attribute name="alignshape" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="focus" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="focussize" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="focusposition" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="method" type="ST_FillMethod" use="optional"/>
|
||||
<xsd:attribute ref="o:detectmouseclick"/>
|
||||
<xsd:attribute ref="o:title"/>
|
||||
<xsd:attribute ref="o:opacity2"/>
|
||||
<xsd:attribute name="recolor" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="rotate" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute ref="r:id" use="optional"/>
|
||||
<xsd:attribute ref="o:relid" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Formulas">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="f" type="CT_F" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_F">
|
||||
<xsd:attribute name="eqn" type="xsd:string"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Handles">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="h" type="CT_H" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_H">
|
||||
<xsd:attribute name="position" type="xsd:string"/>
|
||||
<xsd:attribute name="polar" type="xsd:string"/>
|
||||
<xsd:attribute name="map" type="xsd:string"/>
|
||||
<xsd:attribute name="invx" type="s:ST_TrueFalse"/>
|
||||
<xsd:attribute name="invy" type="s:ST_TrueFalse"/>
|
||||
<xsd:attribute name="switch" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:attribute name="xrange" type="xsd:string"/>
|
||||
<xsd:attribute name="yrange" type="xsd:string"/>
|
||||
<xsd:attribute name="radiusrange" type="xsd:string"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_ImageData">
|
||||
<xsd:attributeGroup ref="AG_Id"/>
|
||||
<xsd:attributeGroup ref="AG_ImageAttributes"/>
|
||||
<xsd:attributeGroup ref="AG_Chromakey"/>
|
||||
<xsd:attribute name="embosscolor" type="s:ST_ColorType" use="optional"/>
|
||||
<xsd:attribute name="recolortarget" type="s:ST_ColorType"/>
|
||||
<xsd:attribute ref="o:href"/>
|
||||
<xsd:attribute ref="o:althref"/>
|
||||
<xsd:attribute ref="o:title"/>
|
||||
<xsd:attribute ref="o:oleid"/>
|
||||
<xsd:attribute ref="o:detectmouseclick"/>
|
||||
<xsd:attribute ref="o:movie"/>
|
||||
<xsd:attribute ref="o:relid"/>
|
||||
<xsd:attribute ref="r:id"/>
|
||||
<xsd:attribute ref="r:pict"/>
|
||||
<xsd:attribute ref="r:href"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Path">
|
||||
<xsd:attributeGroup ref="AG_Id"/>
|
||||
<xsd:attribute name="v" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="limo" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="textboxrect" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="fillok" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="strokeok" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="shadowok" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="arrowok" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="gradientshapeok" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="textpathok" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="insetpenok" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute ref="o:connecttype"/>
|
||||
<xsd:attribute ref="o:connectlocs"/>
|
||||
<xsd:attribute ref="o:connectangles"/>
|
||||
<xsd:attribute ref="o:extrusionok"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Shadow">
|
||||
<xsd:attributeGroup ref="AG_Id"/>
|
||||
<xsd:attribute name="on" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="type" type="ST_ShadowType" use="optional"/>
|
||||
<xsd:attribute name="obscured" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="color" type="s:ST_ColorType" use="optional"/>
|
||||
<xsd:attribute name="opacity" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="offset" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="color2" type="s:ST_ColorType" use="optional"/>
|
||||
<xsd:attribute name="offset2" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="origin" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="matrix" type="xsd:string" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Stroke">
|
||||
<xsd:sequence>
|
||||
<xsd:element ref="o:left" minOccurs="0"/>
|
||||
<xsd:element ref="o:top" minOccurs="0"/>
|
||||
<xsd:element ref="o:right" minOccurs="0"/>
|
||||
<xsd:element ref="o:bottom" minOccurs="0"/>
|
||||
<xsd:element ref="o:column" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attributeGroup ref="AG_Id"/>
|
||||
<xsd:attributeGroup ref="AG_StrokeAttributes"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Textbox">
|
||||
<xsd:choice>
|
||||
<xsd:element ref="w:txbxContent" minOccurs="0"/>
|
||||
<xsd:any namespace="##local" processContents="skip"/>
|
||||
</xsd:choice>
|
||||
<xsd:attributeGroup ref="AG_Id"/>
|
||||
<xsd:attributeGroup ref="AG_Style"/>
|
||||
<xsd:attribute name="inset" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute ref="o:singleclick"/>
|
||||
<xsd:attribute ref="o:insetmode"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_TextPath">
|
||||
<xsd:attributeGroup ref="AG_Id"/>
|
||||
<xsd:attributeGroup ref="AG_Style"/>
|
||||
<xsd:attribute name="on" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="fitshape" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="fitpath" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="trim" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="xscale" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="string" type="xsd:string" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:element name="arc" type="CT_Arc"/>
|
||||
<xsd:element name="curve" type="CT_Curve"/>
|
||||
<xsd:element name="image" type="CT_Image"/>
|
||||
<xsd:element name="line" type="CT_Line"/>
|
||||
<xsd:element name="oval" type="CT_Oval"/>
|
||||
<xsd:element name="polyline" type="CT_PolyLine"/>
|
||||
<xsd:element name="rect" type="CT_Rect"/>
|
||||
<xsd:element name="roundrect" type="CT_RoundRect"/>
|
||||
<xsd:complexType name="CT_Arc">
|
||||
<xsd:sequence>
|
||||
<xsd:group ref="EG_ShapeElements" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attributeGroup ref="AG_AllCoreAttributes"/>
|
||||
<xsd:attributeGroup ref="AG_AllShapeAttributes"/>
|
||||
<xsd:attribute name="startAngle" type="xsd:decimal" use="optional"/>
|
||||
<xsd:attribute name="endAngle" type="xsd:decimal" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Curve">
|
||||
<xsd:sequence>
|
||||
<xsd:group ref="EG_ShapeElements" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attributeGroup ref="AG_AllCoreAttributes"/>
|
||||
<xsd:attributeGroup ref="AG_AllShapeAttributes"/>
|
||||
<xsd:attribute name="from" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="control1" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="control2" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="to" type="xsd:string" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Image">
|
||||
<xsd:sequence>
|
||||
<xsd:group ref="EG_ShapeElements" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attributeGroup ref="AG_AllCoreAttributes"/>
|
||||
<xsd:attributeGroup ref="AG_AllShapeAttributes"/>
|
||||
<xsd:attributeGroup ref="AG_ImageAttributes"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Line">
|
||||
<xsd:sequence>
|
||||
<xsd:group ref="EG_ShapeElements" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attributeGroup ref="AG_AllCoreAttributes"/>
|
||||
<xsd:attributeGroup ref="AG_AllShapeAttributes"/>
|
||||
<xsd:attribute name="from" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="to" type="xsd:string" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Oval">
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:group ref="EG_ShapeElements" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xsd:choice>
|
||||
<xsd:attributeGroup ref="AG_AllCoreAttributes"/>
|
||||
<xsd:attributeGroup ref="AG_AllShapeAttributes"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_PolyLine">
|
||||
<xsd:choice minOccurs="0" maxOccurs="unbounded">
|
||||
<xsd:group ref="EG_ShapeElements"/>
|
||||
<xsd:element ref="o:ink"/>
|
||||
</xsd:choice>
|
||||
<xsd:attributeGroup ref="AG_AllCoreAttributes"/>
|
||||
<xsd:attributeGroup ref="AG_AllShapeAttributes"/>
|
||||
<xsd:attribute name="points" type="xsd:string" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Rect">
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:group ref="EG_ShapeElements" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xsd:choice>
|
||||
<xsd:attributeGroup ref="AG_AllCoreAttributes"/>
|
||||
<xsd:attributeGroup ref="AG_AllShapeAttributes"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_RoundRect">
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:group ref="EG_ShapeElements" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xsd:choice>
|
||||
<xsd:attributeGroup ref="AG_AllCoreAttributes"/>
|
||||
<xsd:attributeGroup ref="AG_AllShapeAttributes"/>
|
||||
<xsd:attribute name="arcsize" type="xsd:string" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_Ext">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="view"/>
|
||||
<xsd:enumeration value="edit"/>
|
||||
<xsd:enumeration value="backwardCompatible"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_FillType">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="solid"/>
|
||||
<xsd:enumeration value="gradient"/>
|
||||
<xsd:enumeration value="gradientRadial"/>
|
||||
<xsd:enumeration value="tile"/>
|
||||
<xsd:enumeration value="pattern"/>
|
||||
<xsd:enumeration value="frame"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_FillMethod">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="none"/>
|
||||
<xsd:enumeration value="linear"/>
|
||||
<xsd:enumeration value="sigma"/>
|
||||
<xsd:enumeration value="any"/>
|
||||
<xsd:enumeration value="linear sigma"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_ShadowType">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="single"/>
|
||||
<xsd:enumeration value="double"/>
|
||||
<xsd:enumeration value="emboss"/>
|
||||
<xsd:enumeration value="perspective"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_StrokeLineStyle">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="single"/>
|
||||
<xsd:enumeration value="thinThin"/>
|
||||
<xsd:enumeration value="thinThick"/>
|
||||
<xsd:enumeration value="thickThin"/>
|
||||
<xsd:enumeration value="thickBetweenThin"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_StrokeJoinStyle">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="round"/>
|
||||
<xsd:enumeration value="bevel"/>
|
||||
<xsd:enumeration value="miter"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_StrokeEndCap">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="flat"/>
|
||||
<xsd:enumeration value="square"/>
|
||||
<xsd:enumeration value="round"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_StrokeArrowLength">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="short"/>
|
||||
<xsd:enumeration value="medium"/>
|
||||
<xsd:enumeration value="long"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_StrokeArrowWidth">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="narrow"/>
|
||||
<xsd:enumeration value="medium"/>
|
||||
<xsd:enumeration value="wide"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_StrokeArrowType">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="none"/>
|
||||
<xsd:enumeration value="block"/>
|
||||
<xsd:enumeration value="classic"/>
|
||||
<xsd:enumeration value="oval"/>
|
||||
<xsd:enumeration value="diamond"/>
|
||||
<xsd:enumeration value="open"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_ImageAspect">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="ignore"/>
|
||||
<xsd:enumeration value="atMost"/>
|
||||
<xsd:enumeration value="atLeast"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_EditAs">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="canvas"/>
|
||||
<xsd:enumeration value="orgchart"/>
|
||||
<xsd:enumeration value="radial"/>
|
||||
<xsd:enumeration value="cycle"/>
|
||||
<xsd:enumeration value="stacked"/>
|
||||
<xsd:enumeration value="venn"/>
|
||||
<xsd:enumeration value="bullseye"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
</xsd:schema>
|
||||
509
skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd
Executable file
509
skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd
Executable file
@@ -0,0 +1,509 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns="urn:schemas-microsoft-com:office:office" xmlns:v="urn:schemas-microsoft-com:vml"
|
||||
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
|
||||
xmlns:s="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
|
||||
targetNamespace="urn:schemas-microsoft-com:office:office" elementFormDefault="qualified"
|
||||
attributeFormDefault="unqualified">
|
||||
<xsd:import namespace="urn:schemas-microsoft-com:vml" schemaLocation="vml-main.xsd"/>
|
||||
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
|
||||
schemaLocation="shared-relationshipReference.xsd"/>
|
||||
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
|
||||
schemaLocation="shared-commonSimpleTypes.xsd"/>
|
||||
<xsd:attribute name="bwmode" type="ST_BWMode"/>
|
||||
<xsd:attribute name="bwpure" type="ST_BWMode"/>
|
||||
<xsd:attribute name="bwnormal" type="ST_BWMode"/>
|
||||
<xsd:attribute name="targetscreensize" type="ST_ScreenSize"/>
|
||||
<xsd:attribute name="insetmode" type="ST_InsetMode" default="custom"/>
|
||||
<xsd:attribute name="spt" type="xsd:float"/>
|
||||
<xsd:attribute name="wrapcoords" type="xsd:string"/>
|
||||
<xsd:attribute name="oned" type="s:ST_TrueFalse"/>
|
||||
<xsd:attribute name="regroupid" type="xsd:integer"/>
|
||||
<xsd:attribute name="doubleclicknotify" type="s:ST_TrueFalse"/>
|
||||
<xsd:attribute name="connectortype" type="ST_ConnectorType" default="straight"/>
|
||||
<xsd:attribute name="button" type="s:ST_TrueFalse"/>
|
||||
<xsd:attribute name="userhidden" type="s:ST_TrueFalse"/>
|
||||
<xsd:attribute name="forcedash" type="s:ST_TrueFalse"/>
|
||||
<xsd:attribute name="oleicon" type="s:ST_TrueFalse"/>
|
||||
<xsd:attribute name="ole" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:attribute name="preferrelative" type="s:ST_TrueFalse"/>
|
||||
<xsd:attribute name="cliptowrap" type="s:ST_TrueFalse"/>
|
||||
<xsd:attribute name="clip" type="s:ST_TrueFalse"/>
|
||||
<xsd:attribute name="bullet" type="s:ST_TrueFalse"/>
|
||||
<xsd:attribute name="hr" type="s:ST_TrueFalse"/>
|
||||
<xsd:attribute name="hrstd" type="s:ST_TrueFalse"/>
|
||||
<xsd:attribute name="hrnoshade" type="s:ST_TrueFalse"/>
|
||||
<xsd:attribute name="hrpct" type="xsd:float"/>
|
||||
<xsd:attribute name="hralign" type="ST_HrAlign" default="left"/>
|
||||
<xsd:attribute name="allowincell" type="s:ST_TrueFalse"/>
|
||||
<xsd:attribute name="allowoverlap" type="s:ST_TrueFalse"/>
|
||||
<xsd:attribute name="userdrawn" type="s:ST_TrueFalse"/>
|
||||
<xsd:attribute name="bordertopcolor" type="xsd:string"/>
|
||||
<xsd:attribute name="borderleftcolor" type="xsd:string"/>
|
||||
<xsd:attribute name="borderbottomcolor" type="xsd:string"/>
|
||||
<xsd:attribute name="borderrightcolor" type="xsd:string"/>
|
||||
<xsd:attribute name="connecttype" type="ST_ConnectType"/>
|
||||
<xsd:attribute name="connectlocs" type="xsd:string"/>
|
||||
<xsd:attribute name="connectangles" type="xsd:string"/>
|
||||
<xsd:attribute name="master" type="xsd:string"/>
|
||||
<xsd:attribute name="extrusionok" type="s:ST_TrueFalse"/>
|
||||
<xsd:attribute name="href" type="xsd:string"/>
|
||||
<xsd:attribute name="althref" type="xsd:string"/>
|
||||
<xsd:attribute name="title" type="xsd:string"/>
|
||||
<xsd:attribute name="singleclick" type="s:ST_TrueFalse"/>
|
||||
<xsd:attribute name="oleid" type="xsd:float"/>
|
||||
<xsd:attribute name="detectmouseclick" type="s:ST_TrueFalse"/>
|
||||
<xsd:attribute name="movie" type="xsd:float"/>
|
||||
<xsd:attribute name="spid" type="xsd:string"/>
|
||||
<xsd:attribute name="opacity2" type="xsd:string"/>
|
||||
<xsd:attribute name="relid" type="r:ST_RelationshipId"/>
|
||||
<xsd:attribute name="dgmlayout" type="ST_DiagramLayout"/>
|
||||
<xsd:attribute name="dgmnodekind" type="xsd:integer"/>
|
||||
<xsd:attribute name="dgmlayoutmru" type="ST_DiagramLayout"/>
|
||||
<xsd:attribute name="gfxdata" type="xsd:base64Binary"/>
|
||||
<xsd:attribute name="tableproperties" type="xsd:string"/>
|
||||
<xsd:attribute name="tablelimits" type="xsd:string"/>
|
||||
<xsd:element name="shapedefaults" type="CT_ShapeDefaults"/>
|
||||
<xsd:element name="shapelayout" type="CT_ShapeLayout"/>
|
||||
<xsd:element name="signatureline" type="CT_SignatureLine"/>
|
||||
<xsd:element name="ink" type="CT_Ink"/>
|
||||
<xsd:element name="diagram" type="CT_Diagram"/>
|
||||
<xsd:element name="equationxml" type="CT_EquationXml"/>
|
||||
<xsd:complexType name="CT_ShapeDefaults">
|
||||
<xsd:all minOccurs="0">
|
||||
<xsd:element ref="v:fill" minOccurs="0"/>
|
||||
<xsd:element ref="v:stroke" minOccurs="0"/>
|
||||
<xsd:element ref="v:textbox" minOccurs="0"/>
|
||||
<xsd:element ref="v:shadow" minOccurs="0"/>
|
||||
<xsd:element ref="skew" minOccurs="0"/>
|
||||
<xsd:element ref="extrusion" minOccurs="0"/>
|
||||
<xsd:element ref="callout" minOccurs="0"/>
|
||||
<xsd:element ref="lock" minOccurs="0"/>
|
||||
<xsd:element name="colormru" minOccurs="0" type="CT_ColorMru"/>
|
||||
<xsd:element name="colormenu" minOccurs="0" type="CT_ColorMenu"/>
|
||||
</xsd:all>
|
||||
<xsd:attributeGroup ref="v:AG_Ext"/>
|
||||
<xsd:attribute name="spidmax" type="xsd:integer" use="optional"/>
|
||||
<xsd:attribute name="style" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="fill" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="fillcolor" type="s:ST_ColorType" use="optional"/>
|
||||
<xsd:attribute name="stroke" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="strokecolor" type="s:ST_ColorType"/>
|
||||
<xsd:attribute name="allowincell" form="qualified" type="s:ST_TrueFalse"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Ink">
|
||||
<xsd:sequence/>
|
||||
<xsd:attribute name="i" type="xsd:string"/>
|
||||
<xsd:attribute name="annotation" type="s:ST_TrueFalse"/>
|
||||
<xsd:attribute name="contentType" type="ST_ContentType" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_SignatureLine">
|
||||
<xsd:attributeGroup ref="v:AG_Ext"/>
|
||||
<xsd:attribute name="issignatureline" type="s:ST_TrueFalse"/>
|
||||
<xsd:attribute name="id" type="s:ST_Guid"/>
|
||||
<xsd:attribute name="provid" type="s:ST_Guid"/>
|
||||
<xsd:attribute name="signinginstructionsset" type="s:ST_TrueFalse"/>
|
||||
<xsd:attribute name="allowcomments" type="s:ST_TrueFalse"/>
|
||||
<xsd:attribute name="showsigndate" type="s:ST_TrueFalse"/>
|
||||
<xsd:attribute name="suggestedsigner" type="xsd:string" form="qualified"/>
|
||||
<xsd:attribute name="suggestedsigner2" type="xsd:string" form="qualified"/>
|
||||
<xsd:attribute name="suggestedsigneremail" type="xsd:string" form="qualified"/>
|
||||
<xsd:attribute name="signinginstructions" type="xsd:string"/>
|
||||
<xsd:attribute name="addlxml" type="xsd:string"/>
|
||||
<xsd:attribute name="sigprovurl" type="xsd:string"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_ShapeLayout">
|
||||
<xsd:all>
|
||||
<xsd:element name="idmap" type="CT_IdMap" minOccurs="0"/>
|
||||
<xsd:element name="regrouptable" type="CT_RegroupTable" minOccurs="0"/>
|
||||
<xsd:element name="rules" type="CT_Rules" minOccurs="0"/>
|
||||
</xsd:all>
|
||||
<xsd:attributeGroup ref="v:AG_Ext"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_IdMap">
|
||||
<xsd:attributeGroup ref="v:AG_Ext"/>
|
||||
<xsd:attribute name="data" type="xsd:string" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_RegroupTable">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="entry" type="CT_Entry" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attributeGroup ref="v:AG_Ext"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Entry">
|
||||
<xsd:attribute name="new" type="xsd:int" use="optional"/>
|
||||
<xsd:attribute name="old" type="xsd:int" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Rules">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="r" type="CT_R" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attributeGroup ref="v:AG_Ext"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_R">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="proxy" type="CT_Proxy" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="id" type="xsd:string" use="required"/>
|
||||
<xsd:attribute name="type" type="ST_RType" use="optional"/>
|
||||
<xsd:attribute name="how" type="ST_How" use="optional"/>
|
||||
<xsd:attribute name="idref" type="xsd:string" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Proxy">
|
||||
<xsd:attribute name="start" type="s:ST_TrueFalseBlank" use="optional" default="false"/>
|
||||
<xsd:attribute name="end" type="s:ST_TrueFalseBlank" use="optional" default="false"/>
|
||||
<xsd:attribute name="idref" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="connectloc" type="xsd:int" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Diagram">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="relationtable" type="CT_RelationTable" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attributeGroup ref="v:AG_Ext"/>
|
||||
<xsd:attribute name="dgmstyle" type="xsd:integer" use="optional"/>
|
||||
<xsd:attribute name="autoformat" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="reverse" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="autolayout" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="dgmscalex" type="xsd:integer" use="optional"/>
|
||||
<xsd:attribute name="dgmscaley" type="xsd:integer" use="optional"/>
|
||||
<xsd:attribute name="dgmfontsize" type="xsd:integer" use="optional"/>
|
||||
<xsd:attribute name="constrainbounds" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="dgmbasetextscale" type="xsd:integer" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_EquationXml">
|
||||
<xsd:sequence>
|
||||
<xsd:any namespace="##any"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="contentType" type="ST_AlternateMathContentType" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_AlternateMathContentType">
|
||||
<xsd:restriction base="xsd:string"/>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_RelationTable">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="rel" type="CT_Relation" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attributeGroup ref="v:AG_Ext"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Relation">
|
||||
<xsd:attributeGroup ref="v:AG_Ext"/>
|
||||
<xsd:attribute name="idsrc" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="iddest" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="idcntr" type="xsd:string" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_ColorMru">
|
||||
<xsd:attributeGroup ref="v:AG_Ext"/>
|
||||
<xsd:attribute name="colors" type="xsd:string"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_ColorMenu">
|
||||
<xsd:attributeGroup ref="v:AG_Ext"/>
|
||||
<xsd:attribute name="strokecolor" type="s:ST_ColorType"/>
|
||||
<xsd:attribute name="fillcolor" type="s:ST_ColorType"/>
|
||||
<xsd:attribute name="shadowcolor" type="s:ST_ColorType"/>
|
||||
<xsd:attribute name="extrusioncolor" type="s:ST_ColorType"/>
|
||||
</xsd:complexType>
|
||||
<xsd:element name="skew" type="CT_Skew"/>
|
||||
<xsd:element name="extrusion" type="CT_Extrusion"/>
|
||||
<xsd:element name="callout" type="CT_Callout"/>
|
||||
<xsd:element name="lock" type="CT_Lock"/>
|
||||
<xsd:element name="OLEObject" type="CT_OLEObject"/>
|
||||
<xsd:element name="complex" type="CT_Complex"/>
|
||||
<xsd:element name="left" type="CT_StrokeChild"/>
|
||||
<xsd:element name="top" type="CT_StrokeChild"/>
|
||||
<xsd:element name="right" type="CT_StrokeChild"/>
|
||||
<xsd:element name="bottom" type="CT_StrokeChild"/>
|
||||
<xsd:element name="column" type="CT_StrokeChild"/>
|
||||
<xsd:element name="clippath" type="CT_ClipPath"/>
|
||||
<xsd:element name="fill" type="CT_Fill"/>
|
||||
<xsd:complexType name="CT_Skew">
|
||||
<xsd:attributeGroup ref="v:AG_Ext"/>
|
||||
<xsd:attribute name="id" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="on" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="offset" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="origin" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="matrix" type="xsd:string" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Extrusion">
|
||||
<xsd:attributeGroup ref="v:AG_Ext"/>
|
||||
<xsd:attribute name="on" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="type" type="ST_ExtrusionType" default="parallel" use="optional"/>
|
||||
<xsd:attribute name="render" type="ST_ExtrusionRender" default="solid" use="optional"/>
|
||||
<xsd:attribute name="viewpointorigin" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="viewpoint" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="plane" type="ST_ExtrusionPlane" default="XY" use="optional"/>
|
||||
<xsd:attribute name="skewangle" type="xsd:float" use="optional"/>
|
||||
<xsd:attribute name="skewamt" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="foredepth" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="backdepth" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="orientation" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="orientationangle" type="xsd:float" use="optional"/>
|
||||
<xsd:attribute name="lockrotationcenter" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="autorotationcenter" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="rotationcenter" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="rotationangle" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="colormode" type="ST_ColorMode" use="optional"/>
|
||||
<xsd:attribute name="color" type="s:ST_ColorType" use="optional"/>
|
||||
<xsd:attribute name="shininess" type="xsd:float" use="optional"/>
|
||||
<xsd:attribute name="specularity" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="diffusity" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="metal" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="edge" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="facet" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="lightface" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="brightness" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="lightposition" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="lightlevel" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="lightharsh" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="lightposition2" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="lightlevel2" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="lightharsh2" type="s:ST_TrueFalse" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Callout">
|
||||
<xsd:attributeGroup ref="v:AG_Ext"/>
|
||||
<xsd:attribute name="on" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="type" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="gap" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="angle" type="ST_Angle" use="optional"/>
|
||||
<xsd:attribute name="dropauto" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="drop" type="ST_CalloutDrop" use="optional"/>
|
||||
<xsd:attribute name="distance" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="lengthspecified" type="s:ST_TrueFalse" default="f" use="optional"/>
|
||||
<xsd:attribute name="length" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="accentbar" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="textborder" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="minusx" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="minusy" type="s:ST_TrueFalse" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Lock">
|
||||
<xsd:attributeGroup ref="v:AG_Ext"/>
|
||||
<xsd:attribute name="position" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="selection" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="grouping" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="ungrouping" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="rotation" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="cropping" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="verticies" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="adjusthandles" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="text" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="aspectratio" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="shapetype" type="s:ST_TrueFalse" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_OLEObject">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="LinkType" type="ST_OLELinkType" minOccurs="0"/>
|
||||
<xsd:element name="LockedField" type="s:ST_TrueFalseBlank" minOccurs="0"/>
|
||||
<xsd:element name="FieldCodes" type="xsd:string" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="Type" type="ST_OLEType" use="optional"/>
|
||||
<xsd:attribute name="ProgID" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="ShapeID" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="DrawAspect" type="ST_OLEDrawAspect" use="optional"/>
|
||||
<xsd:attribute name="ObjectID" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute ref="r:id" use="optional"/>
|
||||
<xsd:attribute name="UpdateMode" type="ST_OLEUpdateMode" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Complex">
|
||||
<xsd:attributeGroup ref="v:AG_Ext"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_StrokeChild">
|
||||
<xsd:attributeGroup ref="v:AG_Ext"/>
|
||||
<xsd:attribute name="on" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="weight" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="color" type="s:ST_ColorType" use="optional"/>
|
||||
<xsd:attribute name="color2" type="s:ST_ColorType" use="optional"/>
|
||||
<xsd:attribute name="opacity" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="linestyle" type="v:ST_StrokeLineStyle" use="optional"/>
|
||||
<xsd:attribute name="miterlimit" type="xsd:decimal" use="optional"/>
|
||||
<xsd:attribute name="joinstyle" type="v:ST_StrokeJoinStyle" use="optional"/>
|
||||
<xsd:attribute name="endcap" type="v:ST_StrokeEndCap" use="optional"/>
|
||||
<xsd:attribute name="dashstyle" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="insetpen" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="filltype" type="v:ST_FillType" use="optional"/>
|
||||
<xsd:attribute name="src" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="imageaspect" type="v:ST_ImageAspect" use="optional"/>
|
||||
<xsd:attribute name="imagesize" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="imagealignshape" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="startarrow" type="v:ST_StrokeArrowType" use="optional"/>
|
||||
<xsd:attribute name="startarrowwidth" type="v:ST_StrokeArrowWidth" use="optional"/>
|
||||
<xsd:attribute name="startarrowlength" type="v:ST_StrokeArrowLength" use="optional"/>
|
||||
<xsd:attribute name="endarrow" type="v:ST_StrokeArrowType" use="optional"/>
|
||||
<xsd:attribute name="endarrowwidth" type="v:ST_StrokeArrowWidth" use="optional"/>
|
||||
<xsd:attribute name="endarrowlength" type="v:ST_StrokeArrowLength" use="optional"/>
|
||||
<xsd:attribute ref="href"/>
|
||||
<xsd:attribute ref="althref"/>
|
||||
<xsd:attribute ref="title"/>
|
||||
<xsd:attribute ref="forcedash"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_ClipPath">
|
||||
<xsd:attribute name="v" type="xsd:string" use="required" form="qualified"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Fill">
|
||||
<xsd:attributeGroup ref="v:AG_Ext"/>
|
||||
<xsd:attribute name="type" type="ST_FillType"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_RType">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="arc"/>
|
||||
<xsd:enumeration value="callout"/>
|
||||
<xsd:enumeration value="connector"/>
|
||||
<xsd:enumeration value="align"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_How">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="top"/>
|
||||
<xsd:enumeration value="middle"/>
|
||||
<xsd:enumeration value="bottom"/>
|
||||
<xsd:enumeration value="left"/>
|
||||
<xsd:enumeration value="center"/>
|
||||
<xsd:enumeration value="right"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_BWMode">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="color"/>
|
||||
<xsd:enumeration value="auto"/>
|
||||
<xsd:enumeration value="grayScale"/>
|
||||
<xsd:enumeration value="lightGrayscale"/>
|
||||
<xsd:enumeration value="inverseGray"/>
|
||||
<xsd:enumeration value="grayOutline"/>
|
||||
<xsd:enumeration value="highContrast"/>
|
||||
<xsd:enumeration value="black"/>
|
||||
<xsd:enumeration value="white"/>
|
||||
<xsd:enumeration value="hide"/>
|
||||
<xsd:enumeration value="undrawn"/>
|
||||
<xsd:enumeration value="blackTextAndLines"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_ScreenSize">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="544,376"/>
|
||||
<xsd:enumeration value="640,480"/>
|
||||
<xsd:enumeration value="720,512"/>
|
||||
<xsd:enumeration value="800,600"/>
|
||||
<xsd:enumeration value="1024,768"/>
|
||||
<xsd:enumeration value="1152,862"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_InsetMode">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="auto"/>
|
||||
<xsd:enumeration value="custom"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_ColorMode">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="auto"/>
|
||||
<xsd:enumeration value="custom"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_ContentType">
|
||||
<xsd:restriction base="xsd:string"/>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_DiagramLayout">
|
||||
<xsd:restriction base="xsd:integer">
|
||||
<xsd:enumeration value="0"/>
|
||||
<xsd:enumeration value="1"/>
|
||||
<xsd:enumeration value="2"/>
|
||||
<xsd:enumeration value="3"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_ExtrusionType">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="perspective"/>
|
||||
<xsd:enumeration value="parallel"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_ExtrusionRender">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="solid"/>
|
||||
<xsd:enumeration value="wireFrame"/>
|
||||
<xsd:enumeration value="boundingCube"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_ExtrusionPlane">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="XY"/>
|
||||
<xsd:enumeration value="ZX"/>
|
||||
<xsd:enumeration value="YZ"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_Angle">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="any"/>
|
||||
<xsd:enumeration value="30"/>
|
||||
<xsd:enumeration value="45"/>
|
||||
<xsd:enumeration value="60"/>
|
||||
<xsd:enumeration value="90"/>
|
||||
<xsd:enumeration value="auto"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_CalloutDrop">
|
||||
<xsd:restriction base="xsd:string"/>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_CalloutPlacement">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="top"/>
|
||||
<xsd:enumeration value="center"/>
|
||||
<xsd:enumeration value="bottom"/>
|
||||
<xsd:enumeration value="user"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_ConnectorType">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="none"/>
|
||||
<xsd:enumeration value="straight"/>
|
||||
<xsd:enumeration value="elbow"/>
|
||||
<xsd:enumeration value="curved"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_HrAlign">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="left"/>
|
||||
<xsd:enumeration value="right"/>
|
||||
<xsd:enumeration value="center"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_ConnectType">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="none"/>
|
||||
<xsd:enumeration value="rect"/>
|
||||
<xsd:enumeration value="segments"/>
|
||||
<xsd:enumeration value="custom"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_OLELinkType">
|
||||
<xsd:restriction base="xsd:string"/>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_OLEType">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="Embed"/>
|
||||
<xsd:enumeration value="Link"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_OLEDrawAspect">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="Content"/>
|
||||
<xsd:enumeration value="Icon"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_OLEUpdateMode">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="Always"/>
|
||||
<xsd:enumeration value="OnCall"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_FillType">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="gradientCenter"/>
|
||||
<xsd:enumeration value="solid"/>
|
||||
<xsd:enumeration value="pattern"/>
|
||||
<xsd:enumeration value="tile"/>
|
||||
<xsd:enumeration value="frame"/>
|
||||
<xsd:enumeration value="gradientUnscaled"/>
|
||||
<xsd:enumeration value="gradientRadial"/>
|
||||
<xsd:enumeration value="gradient"/>
|
||||
<xsd:enumeration value="background"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
</xsd:schema>
|
||||
12
skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd
Executable file
12
skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd
Executable file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns="urn:schemas-microsoft-com:office:powerpoint"
|
||||
targetNamespace="urn:schemas-microsoft-com:office:powerpoint" elementFormDefault="qualified"
|
||||
attributeFormDefault="unqualified">
|
||||
<xsd:element name="iscomment" type="CT_Empty"/>
|
||||
<xsd:element name="textdata" type="CT_Rel"/>
|
||||
<xsd:complexType name="CT_Empty"/>
|
||||
<xsd:complexType name="CT_Rel">
|
||||
<xsd:attribute name="id" type="xsd:string"/>
|
||||
</xsd:complexType>
|
||||
</xsd:schema>
|
||||
108
skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd
Executable file
108
skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd
Executable file
@@ -0,0 +1,108 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns="urn:schemas-microsoft-com:office:excel"
|
||||
xmlns:s="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
|
||||
targetNamespace="urn:schemas-microsoft-com:office:excel" elementFormDefault="qualified"
|
||||
attributeFormDefault="unqualified">
|
||||
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
|
||||
schemaLocation="shared-commonSimpleTypes.xsd"/>
|
||||
<xsd:element name="ClientData" type="CT_ClientData"/>
|
||||
<xsd:complexType name="CT_ClientData">
|
||||
<xsd:choice minOccurs="0" maxOccurs="unbounded">
|
||||
<xsd:element name="MoveWithCells" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="SizeWithCells" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="Anchor" type="xsd:string"/>
|
||||
<xsd:element name="Locked" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="DefaultSize" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="PrintObject" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="Disabled" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="AutoFill" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="AutoLine" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="AutoPict" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="FmlaMacro" type="xsd:string"/>
|
||||
<xsd:element name="TextHAlign" type="xsd:string"/>
|
||||
<xsd:element name="TextVAlign" type="xsd:string"/>
|
||||
<xsd:element name="LockText" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="JustLastX" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="SecretEdit" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="Default" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="Help" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="Cancel" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="Dismiss" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="Accel" type="xsd:integer"/>
|
||||
<xsd:element name="Accel2" type="xsd:integer"/>
|
||||
<xsd:element name="Row" type="xsd:integer"/>
|
||||
<xsd:element name="Column" type="xsd:integer"/>
|
||||
<xsd:element name="Visible" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="RowHidden" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="ColHidden" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="VTEdit" type="xsd:integer"/>
|
||||
<xsd:element name="MultiLine" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="VScroll" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="ValidIds" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="FmlaRange" type="xsd:string"/>
|
||||
<xsd:element name="WidthMin" type="xsd:integer"/>
|
||||
<xsd:element name="Sel" type="xsd:integer"/>
|
||||
<xsd:element name="NoThreeD2" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="SelType" type="xsd:string"/>
|
||||
<xsd:element name="MultiSel" type="xsd:string"/>
|
||||
<xsd:element name="LCT" type="xsd:string"/>
|
||||
<xsd:element name="ListItem" type="xsd:string"/>
|
||||
<xsd:element name="DropStyle" type="xsd:string"/>
|
||||
<xsd:element name="Colored" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="DropLines" type="xsd:integer"/>
|
||||
<xsd:element name="Checked" type="xsd:integer"/>
|
||||
<xsd:element name="FmlaLink" type="xsd:string"/>
|
||||
<xsd:element name="FmlaPict" type="xsd:string"/>
|
||||
<xsd:element name="NoThreeD" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="FirstButton" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="FmlaGroup" type="xsd:string"/>
|
||||
<xsd:element name="Val" type="xsd:integer"/>
|
||||
<xsd:element name="Min" type="xsd:integer"/>
|
||||
<xsd:element name="Max" type="xsd:integer"/>
|
||||
<xsd:element name="Inc" type="xsd:integer"/>
|
||||
<xsd:element name="Page" type="xsd:integer"/>
|
||||
<xsd:element name="Horiz" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="Dx" type="xsd:integer"/>
|
||||
<xsd:element name="MapOCX" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="CF" type="ST_CF"/>
|
||||
<xsd:element name="Camera" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="RecalcAlways" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="AutoScale" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="DDE" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="UIObj" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="ScriptText" type="xsd:string"/>
|
||||
<xsd:element name="ScriptExtended" type="xsd:string"/>
|
||||
<xsd:element name="ScriptLanguage" type="xsd:nonNegativeInteger"/>
|
||||
<xsd:element name="ScriptLocation" type="xsd:nonNegativeInteger"/>
|
||||
<xsd:element name="FmlaTxbx" type="xsd:string"/>
|
||||
</xsd:choice>
|
||||
<xsd:attribute name="ObjectType" type="ST_ObjectType" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_CF">
|
||||
<xsd:restriction base="xsd:string"/>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_ObjectType">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="Button"/>
|
||||
<xsd:enumeration value="Checkbox"/>
|
||||
<xsd:enumeration value="Dialog"/>
|
||||
<xsd:enumeration value="Drop"/>
|
||||
<xsd:enumeration value="Edit"/>
|
||||
<xsd:enumeration value="GBox"/>
|
||||
<xsd:enumeration value="Label"/>
|
||||
<xsd:enumeration value="LineA"/>
|
||||
<xsd:enumeration value="List"/>
|
||||
<xsd:enumeration value="Movie"/>
|
||||
<xsd:enumeration value="Note"/>
|
||||
<xsd:enumeration value="Pict"/>
|
||||
<xsd:enumeration value="Radio"/>
|
||||
<xsd:enumeration value="RectA"/>
|
||||
<xsd:enumeration value="Scroll"/>
|
||||
<xsd:enumeration value="Spin"/>
|
||||
<xsd:enumeration value="Shape"/>
|
||||
<xsd:enumeration value="Group"/>
|
||||
<xsd:enumeration value="Rect"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
</xsd:schema>
|
||||
96
skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd
Executable file
96
skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd
Executable file
@@ -0,0 +1,96 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns="urn:schemas-microsoft-com:office:word"
|
||||
targetNamespace="urn:schemas-microsoft-com:office:word" elementFormDefault="qualified"
|
||||
attributeFormDefault="unqualified">
|
||||
<xsd:element name="bordertop" type="CT_Border"/>
|
||||
<xsd:element name="borderleft" type="CT_Border"/>
|
||||
<xsd:element name="borderright" type="CT_Border"/>
|
||||
<xsd:element name="borderbottom" type="CT_Border"/>
|
||||
<xsd:complexType name="CT_Border">
|
||||
<xsd:attribute name="type" type="ST_BorderType" use="optional"/>
|
||||
<xsd:attribute name="width" type="xsd:positiveInteger" use="optional"/>
|
||||
<xsd:attribute name="shadow" type="ST_BorderShadow" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:element name="wrap" type="CT_Wrap"/>
|
||||
<xsd:complexType name="CT_Wrap">
|
||||
<xsd:attribute name="type" type="ST_WrapType" use="optional"/>
|
||||
<xsd:attribute name="side" type="ST_WrapSide" use="optional"/>
|
||||
<xsd:attribute name="anchorx" type="ST_HorizontalAnchor" use="optional"/>
|
||||
<xsd:attribute name="anchory" type="ST_VerticalAnchor" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:element name="anchorlock" type="CT_AnchorLock"/>
|
||||
<xsd:complexType name="CT_AnchorLock"/>
|
||||
<xsd:simpleType name="ST_BorderType">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="none"/>
|
||||
<xsd:enumeration value="single"/>
|
||||
<xsd:enumeration value="thick"/>
|
||||
<xsd:enumeration value="double"/>
|
||||
<xsd:enumeration value="hairline"/>
|
||||
<xsd:enumeration value="dot"/>
|
||||
<xsd:enumeration value="dash"/>
|
||||
<xsd:enumeration value="dotDash"/>
|
||||
<xsd:enumeration value="dashDotDot"/>
|
||||
<xsd:enumeration value="triple"/>
|
||||
<xsd:enumeration value="thinThickSmall"/>
|
||||
<xsd:enumeration value="thickThinSmall"/>
|
||||
<xsd:enumeration value="thickBetweenThinSmall"/>
|
||||
<xsd:enumeration value="thinThick"/>
|
||||
<xsd:enumeration value="thickThin"/>
|
||||
<xsd:enumeration value="thickBetweenThin"/>
|
||||
<xsd:enumeration value="thinThickLarge"/>
|
||||
<xsd:enumeration value="thickThinLarge"/>
|
||||
<xsd:enumeration value="thickBetweenThinLarge"/>
|
||||
<xsd:enumeration value="wave"/>
|
||||
<xsd:enumeration value="doubleWave"/>
|
||||
<xsd:enumeration value="dashedSmall"/>
|
||||
<xsd:enumeration value="dashDotStroked"/>
|
||||
<xsd:enumeration value="threeDEmboss"/>
|
||||
<xsd:enumeration value="threeDEngrave"/>
|
||||
<xsd:enumeration value="HTMLOutset"/>
|
||||
<xsd:enumeration value="HTMLInset"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_BorderShadow">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="t"/>
|
||||
<xsd:enumeration value="true"/>
|
||||
<xsd:enumeration value="f"/>
|
||||
<xsd:enumeration value="false"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_WrapType">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="topAndBottom"/>
|
||||
<xsd:enumeration value="square"/>
|
||||
<xsd:enumeration value="none"/>
|
||||
<xsd:enumeration value="tight"/>
|
||||
<xsd:enumeration value="through"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_WrapSide">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="both"/>
|
||||
<xsd:enumeration value="left"/>
|
||||
<xsd:enumeration value="right"/>
|
||||
<xsd:enumeration value="largest"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_HorizontalAnchor">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="margin"/>
|
||||
<xsd:enumeration value="page"/>
|
||||
<xsd:enumeration value="text"/>
|
||||
<xsd:enumeration value="char"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_VerticalAnchor">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="margin"/>
|
||||
<xsd:enumeration value="page"/>
|
||||
<xsd:enumeration value="text"/>
|
||||
<xsd:enumeration value="line"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
</xsd:schema>
|
||||
3646
skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd
Executable file
3646
skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd
Executable file
File diff suppressed because it is too large
Load Diff
116
skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd
Executable file
116
skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd
Executable file
@@ -0,0 +1,116 @@
|
||||
<?xml version='1.0'?>
|
||||
<xs:schema targetNamespace="http://www.w3.org/XML/1998/namespace" xmlns:xs="http://www.w3.org/2001/XMLSchema" xml:lang="en">
|
||||
|
||||
<xs:annotation>
|
||||
<xs:documentation>
|
||||
See http://www.w3.org/XML/1998/namespace.html and
|
||||
http://www.w3.org/TR/REC-xml for information about this namespace.
|
||||
|
||||
This schema document describes the XML namespace, in a form
|
||||
suitable for import by other schema documents.
|
||||
|
||||
Note that local names in this namespace are intended to be defined
|
||||
only by the World Wide Web Consortium or its subgroups. The
|
||||
following names are currently defined in this namespace and should
|
||||
not be used with conflicting semantics by any Working Group,
|
||||
specification, or document instance:
|
||||
|
||||
base (as an attribute name): denotes an attribute whose value
|
||||
provides a URI to be used as the base for interpreting any
|
||||
relative URIs in the scope of the element on which it
|
||||
appears; its value is inherited. This name is reserved
|
||||
by virtue of its definition in the XML Base specification.
|
||||
|
||||
lang (as an attribute name): denotes an attribute whose value
|
||||
is a language code for the natural language of the content of
|
||||
any element; its value is inherited. This name is reserved
|
||||
by virtue of its definition in the XML specification.
|
||||
|
||||
space (as an attribute name): denotes an attribute whose
|
||||
value is a keyword indicating what whitespace processing
|
||||
discipline is intended for the content of the element; its
|
||||
value is inherited. This name is reserved by virtue of its
|
||||
definition in the XML specification.
|
||||
|
||||
Father (in any context at all): denotes Jon Bosak, the chair of
|
||||
the original XML Working Group. This name is reserved by
|
||||
the following decision of the W3C XML Plenary and
|
||||
XML Coordination groups:
|
||||
|
||||
In appreciation for his vision, leadership and dedication
|
||||
the W3C XML Plenary on this 10th day of February, 2000
|
||||
reserves for Jon Bosak in perpetuity the XML name
|
||||
xml:Father
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
|
||||
<xs:annotation>
|
||||
<xs:documentation>This schema defines attributes and an attribute group
|
||||
suitable for use by
|
||||
schemas wishing to allow xml:base, xml:lang or xml:space attributes
|
||||
on elements they define.
|
||||
|
||||
To enable this, such a schema must import this schema
|
||||
for the XML namespace, e.g. as follows:
|
||||
<schema . . .>
|
||||
. . .
|
||||
<import namespace="http://www.w3.org/XML/1998/namespace"
|
||||
schemaLocation="http://www.w3.org/2001/03/xml.xsd"/>
|
||||
|
||||
Subsequently, qualified reference to any of the attributes
|
||||
or the group defined below will have the desired effect, e.g.
|
||||
|
||||
<type . . .>
|
||||
. . .
|
||||
<attributeGroup ref="xml:specialAttrs"/>
|
||||
|
||||
will define a type which will schema-validate an instance
|
||||
element with any of those attributes</xs:documentation>
|
||||
</xs:annotation>
|
||||
|
||||
<xs:annotation>
|
||||
<xs:documentation>In keeping with the XML Schema WG's standard versioning
|
||||
policy, this schema document will persist at
|
||||
http://www.w3.org/2001/03/xml.xsd.
|
||||
At the date of issue it can also be found at
|
||||
http://www.w3.org/2001/xml.xsd.
|
||||
The schema document at that URI may however change in the future,
|
||||
in order to remain compatible with the latest version of XML Schema
|
||||
itself. In other words, if the XML Schema namespace changes, the version
|
||||
of this document at
|
||||
http://www.w3.org/2001/xml.xsd will change
|
||||
accordingly; the version at
|
||||
http://www.w3.org/2001/03/xml.xsd will not change.
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
|
||||
<xs:attribute name="lang" type="xs:language">
|
||||
<xs:annotation>
|
||||
<xs:documentation>In due course, we should install the relevant ISO 2- and 3-letter
|
||||
codes as the enumerated possible values . . .</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
|
||||
<xs:attribute name="space" default="preserve">
|
||||
<xs:simpleType>
|
||||
<xs:restriction base="xs:NCName">
|
||||
<xs:enumeration value="default"/>
|
||||
<xs:enumeration value="preserve"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
</xs:attribute>
|
||||
|
||||
<xs:attribute name="base" type="xs:anyURI">
|
||||
<xs:annotation>
|
||||
<xs:documentation>See http://www.w3.org/TR/xmlbase/ for
|
||||
information about this attribute.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
|
||||
<xs:attributeGroup name="specialAttrs">
|
||||
<xs:attribute ref="xml:base"/>
|
||||
<xs:attribute ref="xml:lang"/>
|
||||
<xs:attribute ref="xml:space"/>
|
||||
</xs:attributeGroup>
|
||||
|
||||
</xs:schema>
|
||||
42
skills/docx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd
Executable file
42
skills/docx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd
Executable file
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<xs:schema xmlns="http://schemas.openxmlformats.org/package/2006/content-types"
|
||||
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
targetNamespace="http://schemas.openxmlformats.org/package/2006/content-types"
|
||||
elementFormDefault="qualified" attributeFormDefault="unqualified" blockDefault="#all">
|
||||
|
||||
<xs:element name="Types" type="CT_Types"/>
|
||||
<xs:element name="Default" type="CT_Default"/>
|
||||
<xs:element name="Override" type="CT_Override"/>
|
||||
|
||||
<xs:complexType name="CT_Types">
|
||||
<xs:choice minOccurs="0" maxOccurs="unbounded">
|
||||
<xs:element ref="Default"/>
|
||||
<xs:element ref="Override"/>
|
||||
</xs:choice>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="CT_Default">
|
||||
<xs:attribute name="Extension" type="ST_Extension" use="required"/>
|
||||
<xs:attribute name="ContentType" type="ST_ContentType" use="required"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="CT_Override">
|
||||
<xs:attribute name="ContentType" type="ST_ContentType" use="required"/>
|
||||
<xs:attribute name="PartName" type="xs:anyURI" use="required"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:simpleType name="ST_ContentType">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:pattern
|
||||
value="(((([\p{IsBasicLatin}-[\p{Cc}\(\)<>@,;:\\"/\[\]\?=\{\}\s\t]])+))/((([\p{IsBasicLatin}-[\p{Cc}\(\)<>@,;:\\"/\[\]\?=\{\}\s\t]])+))((\s+)*;(\s+)*(((([\p{IsBasicLatin}-[\p{Cc}\(\)<>@,;:\\"/\[\]\?=\{\}\s\t]])+))=((([\p{IsBasicLatin}-[\p{Cc}\(\)<>@,;:\\"/\[\]\?=\{\}\s\t]])+)|("(([\p{IsLatin-1Supplement}\p{IsBasicLatin}-[\p{Cc}"\n\r]]|(\s+))|(\\[\p{IsBasicLatin}]))*"))))*)"
|
||||
/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:simpleType name="ST_Extension">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:pattern
|
||||
value="([!$&'\(\)\*\+,:=]|(%[0-9a-fA-F][0-9a-fA-F])|[:@]|[a-zA-Z0-9\-_~])+"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
</xs:schema>
|
||||
50
skills/docx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd
Executable file
50
skills/docx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd
Executable file
@@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xs:schema targetNamespace="http://schemas.openxmlformats.org/package/2006/metadata/core-properties"
|
||||
xmlns="http://schemas.openxmlformats.org/package/2006/metadata/core-properties"
|
||||
xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:dcterms="http://purl.org/dc/terms/" elementFormDefault="qualified" blockDefault="#all">
|
||||
|
||||
<xs:import namespace="http://purl.org/dc/elements/1.1/"
|
||||
schemaLocation="http://dublincore.org/schemas/xmls/qdc/2003/04/02/dc.xsd"/>
|
||||
<xs:import namespace="http://purl.org/dc/terms/"
|
||||
schemaLocation="http://dublincore.org/schemas/xmls/qdc/2003/04/02/dcterms.xsd"/>
|
||||
<xs:import id="xml" namespace="http://www.w3.org/XML/1998/namespace"/>
|
||||
|
||||
<xs:element name="coreProperties" type="CT_CoreProperties"/>
|
||||
|
||||
<xs:complexType name="CT_CoreProperties">
|
||||
<xs:all>
|
||||
<xs:element name="category" minOccurs="0" maxOccurs="1" type="xs:string"/>
|
||||
<xs:element name="contentStatus" minOccurs="0" maxOccurs="1" type="xs:string"/>
|
||||
<xs:element ref="dcterms:created" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element ref="dc:creator" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element ref="dc:description" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element ref="dc:identifier" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="keywords" minOccurs="0" maxOccurs="1" type="CT_Keywords"/>
|
||||
<xs:element ref="dc:language" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="lastModifiedBy" minOccurs="0" maxOccurs="1" type="xs:string"/>
|
||||
<xs:element name="lastPrinted" minOccurs="0" maxOccurs="1" type="xs:dateTime"/>
|
||||
<xs:element ref="dcterms:modified" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="revision" minOccurs="0" maxOccurs="1" type="xs:string"/>
|
||||
<xs:element ref="dc:subject" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element ref="dc:title" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="version" minOccurs="0" maxOccurs="1" type="xs:string"/>
|
||||
</xs:all>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="CT_Keywords" mixed="true">
|
||||
<xs:sequence>
|
||||
<xs:element name="value" minOccurs="0" maxOccurs="unbounded" type="CT_Keyword"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute ref="xml:lang" use="optional"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="CT_Keyword">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:string">
|
||||
<xs:attribute ref="xml:lang" use="optional"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
</xs:schema>
|
||||
49
skills/docx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd
Executable file
49
skills/docx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd
Executable file
@@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xsd:schema xmlns="http://schemas.openxmlformats.org/package/2006/digital-signature"
|
||||
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
targetNamespace="http://schemas.openxmlformats.org/package/2006/digital-signature"
|
||||
elementFormDefault="qualified" attributeFormDefault="unqualified" blockDefault="#all">
|
||||
|
||||
<xsd:element name="SignatureTime" type="CT_SignatureTime"/>
|
||||
<xsd:element name="RelationshipReference" type="CT_RelationshipReference"/>
|
||||
<xsd:element name="RelationshipsGroupReference" type="CT_RelationshipsGroupReference"/>
|
||||
|
||||
<xsd:complexType name="CT_SignatureTime">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="Format" type="ST_Format"/>
|
||||
<xsd:element name="Value" type="ST_Value"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="CT_RelationshipReference">
|
||||
<xsd:simpleContent>
|
||||
<xsd:extension base="xsd:string">
|
||||
<xsd:attribute name="SourceId" type="xsd:string" use="required"/>
|
||||
</xsd:extension>
|
||||
</xsd:simpleContent>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="CT_RelationshipsGroupReference">
|
||||
<xsd:simpleContent>
|
||||
<xsd:extension base="xsd:string">
|
||||
<xsd:attribute name="SourceType" type="xsd:anyURI" use="required"/>
|
||||
</xsd:extension>
|
||||
</xsd:simpleContent>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:simpleType name="ST_Format">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:pattern
|
||||
value="(YYYY)|(YYYY-MM)|(YYYY-MM-DD)|(YYYY-MM-DDThh:mmTZD)|(YYYY-MM-DDThh:mm:ssTZD)|(YYYY-MM-DDThh:mm:ss.sTZD)"
|
||||
/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
|
||||
<xsd:simpleType name="ST_Value">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:pattern
|
||||
value="(([0-9][0-9][0-9][0-9]))|(([0-9][0-9][0-9][0-9])-((0[1-9])|(1(0|1|2))))|(([0-9][0-9][0-9][0-9])-((0[1-9])|(1(0|1|2)))-((0[1-9])|(1[0-9])|(2[0-9])|(3(0|1))))|(([0-9][0-9][0-9][0-9])-((0[1-9])|(1(0|1|2)))-((0[1-9])|(1[0-9])|(2[0-9])|(3(0|1)))T((0[0-9])|(1[0-9])|(2(0|1|2|3))):((0[0-9])|(1[0-9])|(2[0-9])|(3[0-9])|(4[0-9])|(5[0-9]))(((\+|-)((0[0-9])|(1[0-9])|(2(0|1|2|3))):((0[0-9])|(1[0-9])|(2[0-9])|(3[0-9])|(4[0-9])|(5[0-9])))|Z))|(([0-9][0-9][0-9][0-9])-((0[1-9])|(1(0|1|2)))-((0[1-9])|(1[0-9])|(2[0-9])|(3(0|1)))T((0[0-9])|(1[0-9])|(2(0|1|2|3))):((0[0-9])|(1[0-9])|(2[0-9])|(3[0-9])|(4[0-9])|(5[0-9])):((0[0-9])|(1[0-9])|(2[0-9])|(3[0-9])|(4[0-9])|(5[0-9]))(((\+|-)((0[0-9])|(1[0-9])|(2(0|1|2|3))):((0[0-9])|(1[0-9])|(2[0-9])|(3[0-9])|(4[0-9])|(5[0-9])))|Z))|(([0-9][0-9][0-9][0-9])-((0[1-9])|(1(0|1|2)))-((0[1-9])|(1[0-9])|(2[0-9])|(3(0|1)))T((0[0-9])|(1[0-9])|(2(0|1|2|3))):((0[0-9])|(1[0-9])|(2[0-9])|(3[0-9])|(4[0-9])|(5[0-9])):(((0[0-9])|(1[0-9])|(2[0-9])|(3[0-9])|(4[0-9])|(5[0-9]))\.[0-9])(((\+|-)((0[0-9])|(1[0-9])|(2(0|1|2|3))):((0[0-9])|(1[0-9])|(2[0-9])|(3[0-9])|(4[0-9])|(5[0-9])))|Z))"
|
||||
/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
</xsd:schema>
|
||||
33
skills/docx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd
Executable file
33
skills/docx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd
Executable file
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<xsd:schema xmlns="http://schemas.openxmlformats.org/package/2006/relationships"
|
||||
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
targetNamespace="http://schemas.openxmlformats.org/package/2006/relationships"
|
||||
elementFormDefault="qualified" attributeFormDefault="unqualified" blockDefault="#all">
|
||||
|
||||
<xsd:element name="Relationships" type="CT_Relationships"/>
|
||||
<xsd:element name="Relationship" type="CT_Relationship"/>
|
||||
|
||||
<xsd:complexType name="CT_Relationships">
|
||||
<xsd:sequence>
|
||||
<xsd:element ref="Relationship" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="CT_Relationship">
|
||||
<xsd:simpleContent>
|
||||
<xsd:extension base="xsd:string">
|
||||
<xsd:attribute name="TargetMode" type="ST_TargetMode" use="optional"/>
|
||||
<xsd:attribute name="Target" type="xsd:anyURI" use="required"/>
|
||||
<xsd:attribute name="Type" type="xsd:anyURI" use="required"/>
|
||||
<xsd:attribute name="Id" type="xsd:ID" use="required"/>
|
||||
</xsd:extension>
|
||||
</xsd:simpleContent>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:simpleType name="ST_TargetMode">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="External"/>
|
||||
<xsd:enumeration value="Internal"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
</xsd:schema>
|
||||
75
skills/docx/ooxml/schemas/mce/mc.xsd
Executable file
75
skills/docx/ooxml/schemas/mce/mc.xsd
Executable file
@@ -0,0 +1,75 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xsd:schema xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
attributeFormDefault="unqualified" elementFormDefault="qualified"
|
||||
targetNamespace="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
||||
|
||||
<!--
|
||||
This XSD is a modified version of the one found at:
|
||||
https://github.com/plutext/docx4j/blob/master/xsd/mce/markup-compatibility-2006-MINIMAL.xsd
|
||||
|
||||
This XSD has 2 objectives:
|
||||
|
||||
1. round tripping @mc:Ignorable
|
||||
|
||||
<w:document
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
|
||||
mc:Ignorable="w14 w15 wp14">
|
||||
|
||||
2. enabling AlternateContent to be manipulated in certain elements
|
||||
(in the unusual case where the content model is xsd:any, it doesn't have to be explicitly added)
|
||||
|
||||
See further ECMA-376, 4th Edition, Office Open XML File Formats
|
||||
Part 3 : Markup Compatibility and Extensibility
|
||||
-->
|
||||
|
||||
<!-- Objective 1 -->
|
||||
<xsd:attribute name="Ignorable" type="xsd:string" />
|
||||
|
||||
<!-- Objective 2 -->
|
||||
<xsd:attribute name="MustUnderstand" type="xsd:string" />
|
||||
<xsd:attribute name="ProcessContent" type="xsd:string" />
|
||||
|
||||
<!-- An AlternateContent element shall contain one or more Choice child elements, optionally followed by a
|
||||
Fallback child element. If present, there shall be only one Fallback element, and it shall follow all Choice
|
||||
elements. -->
|
||||
<xsd:element name="AlternateContent">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="Choice" minOccurs="0" maxOccurs="unbounded">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:any minOccurs="0" maxOccurs="unbounded"
|
||||
processContents="strict">
|
||||
</xsd:any>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="Requires" type="xsd:string" use="required" />
|
||||
<xsd:attribute ref="mc:Ignorable" use="optional" />
|
||||
<xsd:attribute ref="mc:MustUnderstand" use="optional" />
|
||||
<xsd:attribute ref="mc:ProcessContent" use="optional" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="Fallback" minOccurs="0" maxOccurs="1">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:any minOccurs="0" maxOccurs="unbounded"
|
||||
processContents="strict">
|
||||
</xsd:any>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute ref="mc:Ignorable" use="optional" />
|
||||
<xsd:attribute ref="mc:MustUnderstand" use="optional" />
|
||||
<xsd:attribute ref="mc:ProcessContent" use="optional" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:sequence>
|
||||
<!-- AlternateContent elements might include the attributes Ignorable,
|
||||
MustUnderstand and ProcessContent described in this Part of ECMA-376. These
|
||||
attributes’ qualified names shall be prefixed when associated with an AlternateContent
|
||||
element. -->
|
||||
<xsd:attribute ref="mc:Ignorable" use="optional" />
|
||||
<xsd:attribute ref="mc:MustUnderstand" use="optional" />
|
||||
<xsd:attribute ref="mc:ProcessContent" use="optional" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
560
skills/docx/ooxml/schemas/microsoft/wml-2010.xsd
Executable file
560
skills/docx/ooxml/schemas/microsoft/wml-2010.xsd
Executable file
@@ -0,0 +1,560 @@
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:w12="http://schemas.openxmlformats.org/wordprocessingml/2006/main" elementFormDefault="qualified" attributeFormDefault="qualified" blockDefault="#all" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:s="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes" xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns="http://schemas.microsoft.com/office/word/2010/wordml" targetNamespace="http://schemas.microsoft.com/office/word/2010/wordml">
|
||||
<!-- <xsd:import id="rel" namespace="http://schemas.openxmlformats.org/officeDocument/2006/relationships" schemaLocation="orel.xsd"/> -->
|
||||
<xsd:import id="w" namespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main" schemaLocation="../ISO-IEC29500-4_2016/wml.xsd"/>
|
||||
<!-- <xsd:import namespace="http://schemas.openxmlformats.org/drawingml/2006/main" schemaLocation="oartbasetypes.xsd"/>
|
||||
<xsd:import namespace="http://schemas.openxmlformats.org/drawingml/2006/main" schemaLocation="oartsplineproperties.xsd"/> -->
|
||||
<xsd:complexType name="CT_LongHexNumber">
|
||||
<xsd:attribute name="val" type="w:ST_LongHexNumber" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_OnOff">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="true"/>
|
||||
<xsd:enumeration value="false"/>
|
||||
<xsd:enumeration value="0"/>
|
||||
<xsd:enumeration value="1"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_OnOff">
|
||||
<xsd:attribute name="val" type="ST_OnOff"/>
|
||||
</xsd:complexType>
|
||||
<xsd:element name="docId" type="CT_LongHexNumber"/>
|
||||
<xsd:element name="conflictMode" type="CT_OnOff"/>
|
||||
<xsd:attributeGroup name="AG_Parids">
|
||||
<xsd:attribute name="paraId" type="w:ST_LongHexNumber"/>
|
||||
<xsd:attribute name="textId" type="w:ST_LongHexNumber"/>
|
||||
</xsd:attributeGroup>
|
||||
<xsd:attribute name="anchorId" type="w:ST_LongHexNumber"/>
|
||||
<xsd:attribute name="noSpellErr" type="ST_OnOff"/>
|
||||
<xsd:element name="customXmlConflictInsRangeStart" type="w:CT_TrackChange"/>
|
||||
<xsd:element name="customXmlConflictInsRangeEnd" type="w:CT_Markup"/>
|
||||
<xsd:element name="customXmlConflictDelRangeStart" type="w:CT_TrackChange"/>
|
||||
<xsd:element name="customXmlConflictDelRangeEnd" type="w:CT_Markup"/>
|
||||
<xsd:group name="EG_RunLevelConflicts">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="conflictIns" type="w:CT_RunTrackChange" minOccurs="0"/>
|
||||
<xsd:element name="conflictDel" type="w:CT_RunTrackChange" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:group>
|
||||
<xsd:group name="EG_Conflicts">
|
||||
<xsd:choice>
|
||||
<xsd:element name="conflictIns" type="w:CT_TrackChange" minOccurs="0"/>
|
||||
<xsd:element name="conflictDel" type="w:CT_TrackChange" minOccurs="0"/>
|
||||
</xsd:choice>
|
||||
</xsd:group>
|
||||
<xsd:complexType name="CT_Percentage">
|
||||
<xsd:attribute name="val" type="a:ST_Percentage" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_PositiveFixedPercentage">
|
||||
<xsd:attribute name="val" type="a:ST_PositiveFixedPercentage" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_PositivePercentage">
|
||||
<xsd:attribute name="val" type="a:ST_PositivePercentage" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_SchemeColorVal">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="bg1"/>
|
||||
<xsd:enumeration value="tx1"/>
|
||||
<xsd:enumeration value="bg2"/>
|
||||
<xsd:enumeration value="tx2"/>
|
||||
<xsd:enumeration value="accent1"/>
|
||||
<xsd:enumeration value="accent2"/>
|
||||
<xsd:enumeration value="accent3"/>
|
||||
<xsd:enumeration value="accent4"/>
|
||||
<xsd:enumeration value="accent5"/>
|
||||
<xsd:enumeration value="accent6"/>
|
||||
<xsd:enumeration value="hlink"/>
|
||||
<xsd:enumeration value="folHlink"/>
|
||||
<xsd:enumeration value="dk1"/>
|
||||
<xsd:enumeration value="lt1"/>
|
||||
<xsd:enumeration value="dk2"/>
|
||||
<xsd:enumeration value="lt2"/>
|
||||
<xsd:enumeration value="phClr"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_RectAlignment">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="none"/>
|
||||
<xsd:enumeration value="tl"/>
|
||||
<xsd:enumeration value="t"/>
|
||||
<xsd:enumeration value="tr"/>
|
||||
<xsd:enumeration value="l"/>
|
||||
<xsd:enumeration value="ctr"/>
|
||||
<xsd:enumeration value="r"/>
|
||||
<xsd:enumeration value="bl"/>
|
||||
<xsd:enumeration value="b"/>
|
||||
<xsd:enumeration value="br"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_PathShadeType">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="shape"/>
|
||||
<xsd:enumeration value="circle"/>
|
||||
<xsd:enumeration value="rect"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_LineCap">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="rnd"/>
|
||||
<xsd:enumeration value="sq"/>
|
||||
<xsd:enumeration value="flat"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_PresetLineDashVal">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="solid"/>
|
||||
<xsd:enumeration value="dot"/>
|
||||
<xsd:enumeration value="sysDot"/>
|
||||
<xsd:enumeration value="dash"/>
|
||||
<xsd:enumeration value="sysDash"/>
|
||||
<xsd:enumeration value="lgDash"/>
|
||||
<xsd:enumeration value="dashDot"/>
|
||||
<xsd:enumeration value="sysDashDot"/>
|
||||
<xsd:enumeration value="lgDashDot"/>
|
||||
<xsd:enumeration value="lgDashDotDot"/>
|
||||
<xsd:enumeration value="sysDashDotDot"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_PenAlignment">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="ctr"/>
|
||||
<xsd:enumeration value="in"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_CompoundLine">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="sng"/>
|
||||
<xsd:enumeration value="dbl"/>
|
||||
<xsd:enumeration value="thickThin"/>
|
||||
<xsd:enumeration value="thinThick"/>
|
||||
<xsd:enumeration value="tri"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_RelativeRect">
|
||||
<xsd:attribute name="l" use="optional" type="a:ST_Percentage"/>
|
||||
<xsd:attribute name="t" use="optional" type="a:ST_Percentage"/>
|
||||
<xsd:attribute name="r" use="optional" type="a:ST_Percentage"/>
|
||||
<xsd:attribute name="b" use="optional" type="a:ST_Percentage"/>
|
||||
</xsd:complexType>
|
||||
<xsd:group name="EG_ColorTransform">
|
||||
<xsd:choice>
|
||||
<xsd:element name="tint" type="CT_PositiveFixedPercentage"/>
|
||||
<xsd:element name="shade" type="CT_PositiveFixedPercentage"/>
|
||||
<xsd:element name="alpha" type="CT_PositiveFixedPercentage"/>
|
||||
<xsd:element name="hueMod" type="CT_PositivePercentage"/>
|
||||
<xsd:element name="sat" type="CT_Percentage"/>
|
||||
<xsd:element name="satOff" type="CT_Percentage"/>
|
||||
<xsd:element name="satMod" type="CT_Percentage"/>
|
||||
<xsd:element name="lum" type="CT_Percentage"/>
|
||||
<xsd:element name="lumOff" type="CT_Percentage"/>
|
||||
<xsd:element name="lumMod" type="CT_Percentage"/>
|
||||
</xsd:choice>
|
||||
</xsd:group>
|
||||
<xsd:complexType name="CT_SRgbColor">
|
||||
<xsd:sequence>
|
||||
<xsd:group ref="EG_ColorTransform" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="val" type="s:ST_HexColorRGB" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_SchemeColor">
|
||||
<xsd:sequence>
|
||||
<xsd:group ref="EG_ColorTransform" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="val" type="ST_SchemeColorVal" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:group name="EG_ColorChoice">
|
||||
<xsd:choice>
|
||||
<xsd:element name="srgbClr" type="CT_SRgbColor"/>
|
||||
<xsd:element name="schemeClr" type="CT_SchemeColor"/>
|
||||
</xsd:choice>
|
||||
</xsd:group>
|
||||
<xsd:complexType name="CT_Color">
|
||||
<xsd:sequence>
|
||||
<xsd:group ref="EG_ColorChoice"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_GradientStop">
|
||||
<xsd:sequence>
|
||||
<xsd:group ref="EG_ColorChoice"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="pos" type="a:ST_PositiveFixedPercentage" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_GradientStopList">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="gs" type="CT_GradientStop" minOccurs="2" maxOccurs="10"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_LinearShadeProperties">
|
||||
<xsd:attribute name="ang" type="a:ST_PositiveFixedAngle" use="optional"/>
|
||||
<xsd:attribute name="scaled" type="ST_OnOff" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_PathShadeProperties">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="fillToRect" type="CT_RelativeRect" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="path" type="ST_PathShadeType" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:group name="EG_ShadeProperties">
|
||||
<xsd:choice>
|
||||
<xsd:element name="lin" type="CT_LinearShadeProperties"/>
|
||||
<xsd:element name="path" type="CT_PathShadeProperties"/>
|
||||
</xsd:choice>
|
||||
</xsd:group>
|
||||
<xsd:complexType name="CT_SolidColorFillProperties">
|
||||
<xsd:sequence>
|
||||
<xsd:group ref="EG_ColorChoice" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_GradientFillProperties">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="gsLst" type="CT_GradientStopList" minOccurs="0"/>
|
||||
<xsd:group ref="EG_ShadeProperties" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:group name="EG_FillProperties">
|
||||
<xsd:choice>
|
||||
<xsd:element name="noFill" type="w:CT_Empty"/>
|
||||
<xsd:element name="solidFill" type="CT_SolidColorFillProperties"/>
|
||||
<xsd:element name="gradFill" type="CT_GradientFillProperties"/>
|
||||
</xsd:choice>
|
||||
</xsd:group>
|
||||
<xsd:complexType name="CT_PresetLineDashProperties">
|
||||
<xsd:attribute name="val" type="ST_PresetLineDashVal" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:group name="EG_LineDashProperties">
|
||||
<xsd:choice>
|
||||
<xsd:element name="prstDash" type="CT_PresetLineDashProperties"/>
|
||||
</xsd:choice>
|
||||
</xsd:group>
|
||||
<xsd:complexType name="CT_LineJoinMiterProperties">
|
||||
<xsd:attribute name="lim" type="a:ST_PositivePercentage" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:group name="EG_LineJoinProperties">
|
||||
<xsd:choice>
|
||||
<xsd:element name="round" type="w:CT_Empty"/>
|
||||
<xsd:element name="bevel" type="w:CT_Empty"/>
|
||||
<xsd:element name="miter" type="CT_LineJoinMiterProperties"/>
|
||||
</xsd:choice>
|
||||
</xsd:group>
|
||||
<xsd:simpleType name="ST_PresetCameraType">
|
||||
<xsd:restriction base="xsd:token">
|
||||
<xsd:enumeration value="legacyObliqueTopLeft"/>
|
||||
<xsd:enumeration value="legacyObliqueTop"/>
|
||||
<xsd:enumeration value="legacyObliqueTopRight"/>
|
||||
<xsd:enumeration value="legacyObliqueLeft"/>
|
||||
<xsd:enumeration value="legacyObliqueFront"/>
|
||||
<xsd:enumeration value="legacyObliqueRight"/>
|
||||
<xsd:enumeration value="legacyObliqueBottomLeft"/>
|
||||
<xsd:enumeration value="legacyObliqueBottom"/>
|
||||
<xsd:enumeration value="legacyObliqueBottomRight"/>
|
||||
<xsd:enumeration value="legacyPerspectiveTopLeft"/>
|
||||
<xsd:enumeration value="legacyPerspectiveTop"/>
|
||||
<xsd:enumeration value="legacyPerspectiveTopRight"/>
|
||||
<xsd:enumeration value="legacyPerspectiveLeft"/>
|
||||
<xsd:enumeration value="legacyPerspectiveFront"/>
|
||||
<xsd:enumeration value="legacyPerspectiveRight"/>
|
||||
<xsd:enumeration value="legacyPerspectiveBottomLeft"/>
|
||||
<xsd:enumeration value="legacyPerspectiveBottom"/>
|
||||
<xsd:enumeration value="legacyPerspectiveBottomRight"/>
|
||||
<xsd:enumeration value="orthographicFront"/>
|
||||
<xsd:enumeration value="isometricTopUp"/>
|
||||
<xsd:enumeration value="isometricTopDown"/>
|
||||
<xsd:enumeration value="isometricBottomUp"/>
|
||||
<xsd:enumeration value="isometricBottomDown"/>
|
||||
<xsd:enumeration value="isometricLeftUp"/>
|
||||
<xsd:enumeration value="isometricLeftDown"/>
|
||||
<xsd:enumeration value="isometricRightUp"/>
|
||||
<xsd:enumeration value="isometricRightDown"/>
|
||||
<xsd:enumeration value="isometricOffAxis1Left"/>
|
||||
<xsd:enumeration value="isometricOffAxis1Right"/>
|
||||
<xsd:enumeration value="isometricOffAxis1Top"/>
|
||||
<xsd:enumeration value="isometricOffAxis2Left"/>
|
||||
<xsd:enumeration value="isometricOffAxis2Right"/>
|
||||
<xsd:enumeration value="isometricOffAxis2Top"/>
|
||||
<xsd:enumeration value="isometricOffAxis3Left"/>
|
||||
<xsd:enumeration value="isometricOffAxis3Right"/>
|
||||
<xsd:enumeration value="isometricOffAxis3Bottom"/>
|
||||
<xsd:enumeration value="isometricOffAxis4Left"/>
|
||||
<xsd:enumeration value="isometricOffAxis4Right"/>
|
||||
<xsd:enumeration value="isometricOffAxis4Bottom"/>
|
||||
<xsd:enumeration value="obliqueTopLeft"/>
|
||||
<xsd:enumeration value="obliqueTop"/>
|
||||
<xsd:enumeration value="obliqueTopRight"/>
|
||||
<xsd:enumeration value="obliqueLeft"/>
|
||||
<xsd:enumeration value="obliqueRight"/>
|
||||
<xsd:enumeration value="obliqueBottomLeft"/>
|
||||
<xsd:enumeration value="obliqueBottom"/>
|
||||
<xsd:enumeration value="obliqueBottomRight"/>
|
||||
<xsd:enumeration value="perspectiveFront"/>
|
||||
<xsd:enumeration value="perspectiveLeft"/>
|
||||
<xsd:enumeration value="perspectiveRight"/>
|
||||
<xsd:enumeration value="perspectiveAbove"/>
|
||||
<xsd:enumeration value="perspectiveBelow"/>
|
||||
<xsd:enumeration value="perspectiveAboveLeftFacing"/>
|
||||
<xsd:enumeration value="perspectiveAboveRightFacing"/>
|
||||
<xsd:enumeration value="perspectiveContrastingLeftFacing"/>
|
||||
<xsd:enumeration value="perspectiveContrastingRightFacing"/>
|
||||
<xsd:enumeration value="perspectiveHeroicLeftFacing"/>
|
||||
<xsd:enumeration value="perspectiveHeroicRightFacing"/>
|
||||
<xsd:enumeration value="perspectiveHeroicExtremeLeftFacing"/>
|
||||
<xsd:enumeration value="perspectiveHeroicExtremeRightFacing"/>
|
||||
<xsd:enumeration value="perspectiveRelaxed"/>
|
||||
<xsd:enumeration value="perspectiveRelaxedModerately"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_Camera">
|
||||
<xsd:attribute name="prst" use="required" type="ST_PresetCameraType"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_SphereCoords">
|
||||
<xsd:attribute name="lat" type="a:ST_PositiveFixedAngle" use="required"/>
|
||||
<xsd:attribute name="lon" type="a:ST_PositiveFixedAngle" use="required"/>
|
||||
<xsd:attribute name="rev" type="a:ST_PositiveFixedAngle" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_LightRigType">
|
||||
<xsd:restriction base="xsd:token">
|
||||
<xsd:enumeration value="legacyFlat1"/>
|
||||
<xsd:enumeration value="legacyFlat2"/>
|
||||
<xsd:enumeration value="legacyFlat3"/>
|
||||
<xsd:enumeration value="legacyFlat4"/>
|
||||
<xsd:enumeration value="legacyNormal1"/>
|
||||
<xsd:enumeration value="legacyNormal2"/>
|
||||
<xsd:enumeration value="legacyNormal3"/>
|
||||
<xsd:enumeration value="legacyNormal4"/>
|
||||
<xsd:enumeration value="legacyHarsh1"/>
|
||||
<xsd:enumeration value="legacyHarsh2"/>
|
||||
<xsd:enumeration value="legacyHarsh3"/>
|
||||
<xsd:enumeration value="legacyHarsh4"/>
|
||||
<xsd:enumeration value="threePt"/>
|
||||
<xsd:enumeration value="balanced"/>
|
||||
<xsd:enumeration value="soft"/>
|
||||
<xsd:enumeration value="harsh"/>
|
||||
<xsd:enumeration value="flood"/>
|
||||
<xsd:enumeration value="contrasting"/>
|
||||
<xsd:enumeration value="morning"/>
|
||||
<xsd:enumeration value="sunrise"/>
|
||||
<xsd:enumeration value="sunset"/>
|
||||
<xsd:enumeration value="chilly"/>
|
||||
<xsd:enumeration value="freezing"/>
|
||||
<xsd:enumeration value="flat"/>
|
||||
<xsd:enumeration value="twoPt"/>
|
||||
<xsd:enumeration value="glow"/>
|
||||
<xsd:enumeration value="brightRoom"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_LightRigDirection">
|
||||
<xsd:restriction base="xsd:token">
|
||||
<xsd:enumeration value="tl"/>
|
||||
<xsd:enumeration value="t"/>
|
||||
<xsd:enumeration value="tr"/>
|
||||
<xsd:enumeration value="l"/>
|
||||
<xsd:enumeration value="r"/>
|
||||
<xsd:enumeration value="bl"/>
|
||||
<xsd:enumeration value="b"/>
|
||||
<xsd:enumeration value="br"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_LightRig">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="rot" type="CT_SphereCoords" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="rig" type="ST_LightRigType" use="required"/>
|
||||
<xsd:attribute name="dir" type="ST_LightRigDirection" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_BevelPresetType">
|
||||
<xsd:restriction base="xsd:token">
|
||||
<xsd:enumeration value="relaxedInset"/>
|
||||
<xsd:enumeration value="circle"/>
|
||||
<xsd:enumeration value="slope"/>
|
||||
<xsd:enumeration value="cross"/>
|
||||
<xsd:enumeration value="angle"/>
|
||||
<xsd:enumeration value="softRound"/>
|
||||
<xsd:enumeration value="convex"/>
|
||||
<xsd:enumeration value="coolSlant"/>
|
||||
<xsd:enumeration value="divot"/>
|
||||
<xsd:enumeration value="riblet"/>
|
||||
<xsd:enumeration value="hardEdge"/>
|
||||
<xsd:enumeration value="artDeco"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_Bevel">
|
||||
<xsd:attribute name="w" type="a:ST_PositiveCoordinate" use="optional"/>
|
||||
<xsd:attribute name="h" type="a:ST_PositiveCoordinate" use="optional"/>
|
||||
<xsd:attribute name="prst" type="ST_BevelPresetType" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_PresetMaterialType">
|
||||
<xsd:restriction base="xsd:token">
|
||||
<xsd:enumeration value="legacyMatte"/>
|
||||
<xsd:enumeration value="legacyPlastic"/>
|
||||
<xsd:enumeration value="legacyMetal"/>
|
||||
<xsd:enumeration value="legacyWireframe"/>
|
||||
<xsd:enumeration value="matte"/>
|
||||
<xsd:enumeration value="plastic"/>
|
||||
<xsd:enumeration value="metal"/>
|
||||
<xsd:enumeration value="warmMatte"/>
|
||||
<xsd:enumeration value="translucentPowder"/>
|
||||
<xsd:enumeration value="powder"/>
|
||||
<xsd:enumeration value="dkEdge"/>
|
||||
<xsd:enumeration value="softEdge"/>
|
||||
<xsd:enumeration value="clear"/>
|
||||
<xsd:enumeration value="flat"/>
|
||||
<xsd:enumeration value="softmetal"/>
|
||||
<xsd:enumeration value="none"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_Glow">
|
||||
<xsd:sequence>
|
||||
<xsd:group ref="EG_ColorChoice"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="rad" use="optional" type="a:ST_PositiveCoordinate"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Shadow">
|
||||
<xsd:sequence>
|
||||
<xsd:group ref="EG_ColorChoice"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="blurRad" use="optional" type="a:ST_PositiveCoordinate"/>
|
||||
<xsd:attribute name="dist" use="optional" type="a:ST_PositiveCoordinate"/>
|
||||
<xsd:attribute name="dir" use="optional" type="a:ST_PositiveFixedAngle"/>
|
||||
<xsd:attribute name="sx" use="optional" type="a:ST_Percentage"/>
|
||||
<xsd:attribute name="sy" use="optional" type="a:ST_Percentage"/>
|
||||
<xsd:attribute name="kx" use="optional" type="a:ST_FixedAngle"/>
|
||||
<xsd:attribute name="ky" use="optional" type="a:ST_FixedAngle"/>
|
||||
<xsd:attribute name="algn" use="optional" type="ST_RectAlignment"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Reflection">
|
||||
<xsd:attribute name="blurRad" use="optional" type="a:ST_PositiveCoordinate"/>
|
||||
<xsd:attribute name="stA" use="optional" type="a:ST_PositiveFixedPercentage"/>
|
||||
<xsd:attribute name="stPos" use="optional" type="a:ST_PositiveFixedPercentage"/>
|
||||
<xsd:attribute name="endA" use="optional" type="a:ST_PositiveFixedPercentage"/>
|
||||
<xsd:attribute name="endPos" use="optional" type="a:ST_PositiveFixedPercentage"/>
|
||||
<xsd:attribute name="dist" use="optional" type="a:ST_PositiveCoordinate"/>
|
||||
<xsd:attribute name="dir" use="optional" type="a:ST_PositiveFixedAngle"/>
|
||||
<xsd:attribute name="fadeDir" use="optional" type="a:ST_PositiveFixedAngle"/>
|
||||
<xsd:attribute name="sx" use="optional" type="a:ST_Percentage"/>
|
||||
<xsd:attribute name="sy" use="optional" type="a:ST_Percentage"/>
|
||||
<xsd:attribute name="kx" use="optional" type="a:ST_FixedAngle"/>
|
||||
<xsd:attribute name="ky" use="optional" type="a:ST_FixedAngle"/>
|
||||
<xsd:attribute name="algn" use="optional" type="ST_RectAlignment"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_FillTextEffect">
|
||||
<xsd:sequence>
|
||||
<xsd:group ref="EG_FillProperties" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_TextOutlineEffect">
|
||||
<xsd:sequence>
|
||||
<xsd:group ref="EG_FillProperties" minOccurs="0"/>
|
||||
<xsd:group ref="EG_LineDashProperties" minOccurs="0"/>
|
||||
<xsd:group ref="EG_LineJoinProperties" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="w" use="optional" type="a:ST_LineWidth"/>
|
||||
<xsd:attribute name="cap" use="optional" type="ST_LineCap"/>
|
||||
<xsd:attribute name="cmpd" use="optional" type="ST_CompoundLine"/>
|
||||
<xsd:attribute name="algn" use="optional" type="ST_PenAlignment"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Scene3D">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="camera" type="CT_Camera"/>
|
||||
<xsd:element name="lightRig" type="CT_LightRig"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Props3D">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="bevelT" type="CT_Bevel" minOccurs="0"/>
|
||||
<xsd:element name="bevelB" type="CT_Bevel" minOccurs="0"/>
|
||||
<xsd:element name="extrusionClr" type="CT_Color" minOccurs="0"/>
|
||||
<xsd:element name="contourClr" type="CT_Color" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="extrusionH" type="a:ST_PositiveCoordinate" use="optional"/>
|
||||
<xsd:attribute name="contourW" type="a:ST_PositiveCoordinate" use="optional"/>
|
||||
<xsd:attribute name="prstMaterial" type="ST_PresetMaterialType" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:group name="EG_RPrTextEffects">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="glow" minOccurs="0" type="CT_Glow"/>
|
||||
<xsd:element name="shadow" minOccurs="0" type="CT_Shadow"/>
|
||||
<xsd:element name="reflection" minOccurs="0" type="CT_Reflection"/>
|
||||
<xsd:element name="textOutline" minOccurs="0" type="CT_TextOutlineEffect"/>
|
||||
<xsd:element name="textFill" minOccurs="0" type="CT_FillTextEffect"/>
|
||||
<xsd:element name="scene3d" minOccurs="0" type="CT_Scene3D"/>
|
||||
<xsd:element name="props3d" minOccurs="0" type="CT_Props3D"/>
|
||||
</xsd:sequence>
|
||||
</xsd:group>
|
||||
<xsd:simpleType name="ST_Ligatures">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="none"/>
|
||||
<xsd:enumeration value="standard"/>
|
||||
<xsd:enumeration value="contextual"/>
|
||||
<xsd:enumeration value="historical"/>
|
||||
<xsd:enumeration value="discretional"/>
|
||||
<xsd:enumeration value="standardContextual"/>
|
||||
<xsd:enumeration value="standardHistorical"/>
|
||||
<xsd:enumeration value="contextualHistorical"/>
|
||||
<xsd:enumeration value="standardDiscretional"/>
|
||||
<xsd:enumeration value="contextualDiscretional"/>
|
||||
<xsd:enumeration value="historicalDiscretional"/>
|
||||
<xsd:enumeration value="standardContextualHistorical"/>
|
||||
<xsd:enumeration value="standardContextualDiscretional"/>
|
||||
<xsd:enumeration value="standardHistoricalDiscretional"/>
|
||||
<xsd:enumeration value="contextualHistoricalDiscretional"/>
|
||||
<xsd:enumeration value="all"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_Ligatures">
|
||||
<xsd:attribute name="val" type="ST_Ligatures" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_NumForm">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="default"/>
|
||||
<xsd:enumeration value="lining"/>
|
||||
<xsd:enumeration value="oldStyle"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_NumForm">
|
||||
<xsd:attribute name="val" type="ST_NumForm" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_NumSpacing">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="default"/>
|
||||
<xsd:enumeration value="proportional"/>
|
||||
<xsd:enumeration value="tabular"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_NumSpacing">
|
||||
<xsd:attribute name="val" type="ST_NumSpacing" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_StyleSet">
|
||||
<xsd:attribute name="id" type="s:ST_UnsignedDecimalNumber" use="required"/>
|
||||
<xsd:attribute name="val" type="ST_OnOff" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_StylisticSets">
|
||||
<xsd:sequence minOccurs="0">
|
||||
<xsd:element name="styleSet" minOccurs="0" maxOccurs="unbounded" type="CT_StyleSet"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:group name="EG_RPrOpenType">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="ligatures" minOccurs="0" type="CT_Ligatures"/>
|
||||
<xsd:element name="numForm" minOccurs="0" type="CT_NumForm"/>
|
||||
<xsd:element name="numSpacing" minOccurs="0" type="CT_NumSpacing"/>
|
||||
<xsd:element name="stylisticSets" minOccurs="0" type="CT_StylisticSets"/>
|
||||
<xsd:element name="cntxtAlts" minOccurs="0" type="CT_OnOff"/>
|
||||
</xsd:sequence>
|
||||
</xsd:group>
|
||||
<xsd:element name="discardImageEditingData" type="CT_OnOff"/>
|
||||
<xsd:element name="defaultImageDpi" type="CT_DefaultImageDpi"/>
|
||||
<xsd:complexType name="CT_DefaultImageDpi">
|
||||
<xsd:attribute name="val" type="w:ST_DecimalNumber" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:element name="entityPicker" type="w:CT_Empty"/>
|
||||
<xsd:complexType name="CT_SdtCheckboxSymbol">
|
||||
<xsd:attribute name="font" type="s:ST_String"/>
|
||||
<xsd:attribute name="val" type="w:ST_ShortHexNumber"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_SdtCheckbox">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="checked" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="checkedState" type="CT_SdtCheckboxSymbol" minOccurs="0"/>
|
||||
<xsd:element name="uncheckedState" type="CT_SdtCheckboxSymbol" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:element name="checkbox" type="CT_SdtCheckbox"/>
|
||||
</xsd:schema>
|
||||
67
skills/docx/ooxml/schemas/microsoft/wml-2012.xsd
Executable file
67
skills/docx/ooxml/schemas/microsoft/wml-2012.xsd
Executable file
@@ -0,0 +1,67 @@
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:w12="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:s="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes" elementFormDefault="qualified" attributeFormDefault="qualified" blockDefault="#all" xmlns="http://schemas.microsoft.com/office/word/2012/wordml" targetNamespace="http://schemas.microsoft.com/office/word/2012/wordml">
|
||||
<xsd:import id="w12" namespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main" schemaLocation="../ISO-IEC29500-4_2016/wml.xsd"/>
|
||||
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes" schemaLocation="../ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd"/>
|
||||
<xsd:element name="color" type="w12:CT_Color"/>
|
||||
<xsd:simpleType name="ST_SdtAppearance">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="boundingBox"/>
|
||||
<xsd:enumeration value="tags"/>
|
||||
<xsd:enumeration value="hidden"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:element name="dataBinding" type="w12:CT_DataBinding"/>
|
||||
<xsd:complexType name="CT_SdtAppearance">
|
||||
<xsd:attribute name="val" type="ST_SdtAppearance"/>
|
||||
</xsd:complexType>
|
||||
<xsd:element name="appearance" type="CT_SdtAppearance"/>
|
||||
<xsd:complexType name="CT_CommentsEx">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="commentEx" type="CT_CommentEx" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_CommentEx">
|
||||
<xsd:attribute name="paraId" type="w12:ST_LongHexNumber" use="required"/>
|
||||
<xsd:attribute name="paraIdParent" type="w12:ST_LongHexNumber" use="optional"/>
|
||||
<xsd:attribute name="done" type="s:ST_OnOff" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:element name="commentsEx" type="CT_CommentsEx"/>
|
||||
<xsd:complexType name="CT_People">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="person" type="CT_Person" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_PresenceInfo">
|
||||
<xsd:attribute name="providerId" type="xsd:string" use="required"/>
|
||||
<xsd:attribute name="userId" type="xsd:string" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Person">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="presenceInfo" type="CT_PresenceInfo" minOccurs="0" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="author" type="s:ST_String" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:element name="people" type="CT_People"/>
|
||||
<xsd:complexType name="CT_SdtRepeatedSection">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="sectionTitle" type="w12:CT_String" minOccurs="0"/>
|
||||
<xsd:element name="doNotAllowInsertDeleteSection" type="w12:CT_OnOff" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_Guid">
|
||||
<xsd:restriction base="xsd:token">
|
||||
<xsd:pattern value="\{[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}\}"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_Guid">
|
||||
<xsd:attribute name="val" type="ST_Guid"/>
|
||||
</xsd:complexType>
|
||||
<xsd:element name="repeatingSection" type="CT_SdtRepeatedSection"/>
|
||||
<xsd:element name="repeatingSectionItem" type="w12:CT_Empty"/>
|
||||
<xsd:element name="chartTrackingRefBased" type="w12:CT_OnOff"/>
|
||||
<xsd:element name="collapsed" type="w12:CT_OnOff"/>
|
||||
<xsd:element name="docId" type="CT_Guid"/>
|
||||
<xsd:element name="footnoteColumns" type="w12:CT_DecimalNumber"/>
|
||||
<xsd:element name="webExtensionLinked" type="w12:CT_OnOff"/>
|
||||
<xsd:element name="webExtensionCreated" type="w12:CT_OnOff"/>
|
||||
<xsd:attribute name="restartNumberingAfterBreak" type="s:ST_OnOff"/>
|
||||
</xsd:schema>
|
||||
14
skills/docx/ooxml/schemas/microsoft/wml-2018.xsd
Executable file
14
skills/docx/ooxml/schemas/microsoft/wml-2018.xsd
Executable file
@@ -0,0 +1,14 @@
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:w12="http://schemas.openxmlformats.org/wordprocessingml/2006/main" elementFormDefault="qualified" attributeFormDefault="qualified" blockDefault="#all" xmlns="http://schemas.microsoft.com/office/word/2018/wordml" targetNamespace="http://schemas.microsoft.com/office/word/2018/wordml">
|
||||
<xsd:import id="w12" namespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main" schemaLocation="../ISO-IEC29500-4_2016/wml.xsd"/>
|
||||
<xsd:complexType name="CT_Extension">
|
||||
<xsd:sequence>
|
||||
<xsd:any processContents="lax"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="uri" type="xsd:token"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_ExtensionList">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="ext" type="CT_Extension" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
</xsd:schema>
|
||||
20
skills/docx/ooxml/schemas/microsoft/wml-cex-2018.xsd
Executable file
20
skills/docx/ooxml/schemas/microsoft/wml-cex-2018.xsd
Executable file
@@ -0,0 +1,20 @@
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:s="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes" xmlns:w16="http://schemas.microsoft.com/office/word/2018/wordml" elementFormDefault="qualified" attributeFormDefault="qualified" blockDefault="#all" xmlns="http://schemas.microsoft.com/office/word/2018/wordml/cex" targetNamespace="http://schemas.microsoft.com/office/word/2018/wordml/cex">
|
||||
<xsd:import id="w16" namespace="http://schemas.microsoft.com/office/word/2018/wordml" schemaLocation="wml-2018.xsd"/>
|
||||
<xsd:import id="w" namespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main" schemaLocation="../ISO-IEC29500-4_2016/wml.xsd"/>
|
||||
<xsd:import id="s" namespace="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes" schemaLocation="../ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd"/>
|
||||
<xsd:complexType name="CT_CommentsExtensible">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="commentExtensible" type="CT_CommentExtensible" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xsd:element name="extLst" type="w16:CT_ExtensionList" minOccurs="0" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_CommentExtensible">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="extLst" type="w16:CT_ExtensionList" minOccurs="0" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="durableId" type="w:ST_LongHexNumber" use="required"/>
|
||||
<xsd:attribute name="dateUtc" type="w:ST_DateTime" use="optional"/>
|
||||
<xsd:attribute name="intelligentPlaceholder" type="s:ST_OnOff" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:element name="commentsExtensible" type="CT_CommentsExtensible"/>
|
||||
</xsd:schema>
|
||||
13
skills/docx/ooxml/schemas/microsoft/wml-cid-2016.xsd
Executable file
13
skills/docx/ooxml/schemas/microsoft/wml-cid-2016.xsd
Executable file
@@ -0,0 +1,13 @@
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:w12="http://schemas.openxmlformats.org/wordprocessingml/2006/main" elementFormDefault="qualified" attributeFormDefault="qualified" blockDefault="#all" xmlns="http://schemas.microsoft.com/office/word/2016/wordml/cid" targetNamespace="http://schemas.microsoft.com/office/word/2016/wordml/cid">
|
||||
<xsd:import id="w12" namespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main" schemaLocation="../ISO-IEC29500-4_2016/wml.xsd"/>
|
||||
<xsd:complexType name="CT_CommentsIds">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="commentId" type="CT_CommentId" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_CommentId">
|
||||
<xsd:attribute name="paraId" type="w12:ST_LongHexNumber" use="required"/>
|
||||
<xsd:attribute name="durableId" type="w12:ST_LongHexNumber" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:element name="commentsIds" type="CT_CommentsIds"/>
|
||||
</xsd:schema>
|
||||
4
skills/docx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd
Executable file
4
skills/docx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd
Executable file
@@ -0,0 +1,4 @@
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:w12="http://schemas.openxmlformats.org/wordprocessingml/2006/main" elementFormDefault="qualified" attributeFormDefault="qualified" blockDefault="#all" xmlns="http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash" targetNamespace="http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash">
|
||||
<xsd:import id="w12" namespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main" schemaLocation="../ISO-IEC29500-4_2016/wml.xsd"/>
|
||||
<xsd:attribute name="storeItemChecksum" type="w12:ST_String"/>
|
||||
</xsd:schema>
|
||||
8
skills/docx/ooxml/schemas/microsoft/wml-symex-2015.xsd
Executable file
8
skills/docx/ooxml/schemas/microsoft/wml-symex-2015.xsd
Executable file
@@ -0,0 +1,8 @@
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:w12="http://schemas.openxmlformats.org/wordprocessingml/2006/main" elementFormDefault="qualified" attributeFormDefault="qualified" blockDefault="#all" xmlns="http://schemas.microsoft.com/office/word/2015/wordml/symex" targetNamespace="http://schemas.microsoft.com/office/word/2015/wordml/symex">
|
||||
<xsd:import id="w12" namespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main" schemaLocation="../ISO-IEC29500-4_2016/wml.xsd"/>
|
||||
<xsd:complexType name="CT_SymEx">
|
||||
<xsd:attribute name="font" type="w12:ST_String"/>
|
||||
<xsd:attribute name="char" type="w12:ST_LongHexNumber"/>
|
||||
</xsd:complexType>
|
||||
<xsd:element name="symEx" type="CT_SymEx"/>
|
||||
</xsd:schema>
|
||||
159
skills/docx/ooxml/scripts/pack.py
Executable file
159
skills/docx/ooxml/scripts/pack.py
Executable file
@@ -0,0 +1,159 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Tool to pack a directory into a .docx, .pptx, or .xlsx file with XML formatting undone.
|
||||
|
||||
Example usage:
|
||||
python pack.py <input_directory> <office_file> [--force]
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import defusedxml.minidom
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Pack a directory into an Office file")
|
||||
parser.add_argument("input_directory", help="Unpacked Office document directory")
|
||||
parser.add_argument("output_file", help="Output Office file (.docx/.pptx/.xlsx)")
|
||||
parser.add_argument("--force", action="store_true", help="Skip validation")
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
success = pack_document(
|
||||
args.input_directory, args.output_file, validate=not args.force
|
||||
)
|
||||
|
||||
# Show warning if validation was skipped
|
||||
if args.force:
|
||||
print("Warning: Skipped validation, file may be corrupt", file=sys.stderr)
|
||||
# Exit with error if validation failed
|
||||
elif not success:
|
||||
print("Contents would produce a corrupt file.", file=sys.stderr)
|
||||
print("Please validate XML before repacking.", file=sys.stderr)
|
||||
print("Use --force to skip validation and pack anyway.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
except ValueError as e:
|
||||
sys.exit(f"Error: {e}")
|
||||
|
||||
|
||||
def pack_document(input_dir, output_file, validate=False):
|
||||
"""Pack a directory into an Office file (.docx/.pptx/.xlsx).
|
||||
|
||||
Args:
|
||||
input_dir: Path to unpacked Office document directory
|
||||
output_file: Path to output Office file
|
||||
validate: If True, validates with soffice (default: False)
|
||||
|
||||
Returns:
|
||||
bool: True if successful, False if validation failed
|
||||
"""
|
||||
input_dir = Path(input_dir)
|
||||
output_file = Path(output_file)
|
||||
|
||||
if not input_dir.is_dir():
|
||||
raise ValueError(f"{input_dir} is not a directory")
|
||||
if output_file.suffix.lower() not in {".docx", ".pptx", ".xlsx"}:
|
||||
raise ValueError(f"{output_file} must be a .docx, .pptx, or .xlsx file")
|
||||
|
||||
# Work in temporary directory to avoid modifying original
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
temp_content_dir = Path(temp_dir) / "content"
|
||||
shutil.copytree(input_dir, temp_content_dir)
|
||||
|
||||
# Process XML files to remove pretty-printing whitespace
|
||||
for pattern in ["*.xml", "*.rels"]:
|
||||
for xml_file in temp_content_dir.rglob(pattern):
|
||||
condense_xml(xml_file)
|
||||
|
||||
# Create final Office file as zip archive
|
||||
output_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
with zipfile.ZipFile(output_file, "w", zipfile.ZIP_DEFLATED) as zf:
|
||||
for f in temp_content_dir.rglob("*"):
|
||||
if f.is_file():
|
||||
zf.write(f, f.relative_to(temp_content_dir))
|
||||
|
||||
# Validate if requested
|
||||
if validate:
|
||||
if not validate_document(output_file):
|
||||
output_file.unlink() # Delete the corrupt file
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def validate_document(doc_path):
|
||||
"""Validate document by converting to HTML with soffice."""
|
||||
# Determine the correct filter based on file extension
|
||||
match doc_path.suffix.lower():
|
||||
case ".docx":
|
||||
filter_name = "html:HTML"
|
||||
case ".pptx":
|
||||
filter_name = "html:impress_html_Export"
|
||||
case ".xlsx":
|
||||
filter_name = "html:HTML (StarCalc)"
|
||||
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[
|
||||
"soffice",
|
||||
"--headless",
|
||||
"--convert-to",
|
||||
filter_name,
|
||||
"--outdir",
|
||||
temp_dir,
|
||||
str(doc_path),
|
||||
],
|
||||
capture_output=True,
|
||||
timeout=10,
|
||||
text=True,
|
||||
)
|
||||
if not (Path(temp_dir) / f"{doc_path.stem}.html").exists():
|
||||
error_msg = result.stderr.strip() or "Document validation failed"
|
||||
print(f"Validation error: {error_msg}", file=sys.stderr)
|
||||
return False
|
||||
return True
|
||||
except FileNotFoundError:
|
||||
print("Warning: soffice not found. Skipping validation.", file=sys.stderr)
|
||||
return True
|
||||
except subprocess.TimeoutExpired:
|
||||
print("Validation error: Timeout during conversion", file=sys.stderr)
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"Validation error: {e}", file=sys.stderr)
|
||||
return False
|
||||
|
||||
|
||||
def condense_xml(xml_file):
|
||||
"""Strip unnecessary whitespace and remove comments."""
|
||||
with open(xml_file, "r", encoding="utf-8") as f:
|
||||
dom = defusedxml.minidom.parse(f)
|
||||
|
||||
# Process each element to remove whitespace and comments
|
||||
for element in dom.getElementsByTagName("*"):
|
||||
# Skip w:t elements and their processing
|
||||
if element.tagName.endswith(":t"):
|
||||
continue
|
||||
|
||||
# Remove whitespace-only text nodes and comment nodes
|
||||
for child in list(element.childNodes):
|
||||
if (
|
||||
child.nodeType == child.TEXT_NODE
|
||||
and child.nodeValue
|
||||
and child.nodeValue.strip() == ""
|
||||
) or child.nodeType == child.COMMENT_NODE:
|
||||
element.removeChild(child)
|
||||
|
||||
# Write back the condensed XML
|
||||
with open(xml_file, "wb") as f:
|
||||
f.write(dom.toxml(encoding="UTF-8"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
29
skills/docx/ooxml/scripts/unpack.py
Executable file
29
skills/docx/ooxml/scripts/unpack.py
Executable file
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Unpack and format XML contents of Office files (.docx, .pptx, .xlsx)"""
|
||||
|
||||
import random
|
||||
import sys
|
||||
import defusedxml.minidom
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
# Get command line arguments
|
||||
assert len(sys.argv) == 3, "Usage: python unpack.py <office_file> <output_dir>"
|
||||
input_file, output_dir = sys.argv[1], sys.argv[2]
|
||||
|
||||
# Extract and format
|
||||
output_path = Path(output_dir)
|
||||
output_path.mkdir(parents=True, exist_ok=True)
|
||||
zipfile.ZipFile(input_file).extractall(output_path)
|
||||
|
||||
# Pretty print all XML files
|
||||
xml_files = list(output_path.rglob("*.xml")) + list(output_path.rglob("*.rels"))
|
||||
for xml_file in xml_files:
|
||||
content = xml_file.read_text(encoding="utf-8")
|
||||
dom = defusedxml.minidom.parseString(content)
|
||||
xml_file.write_bytes(dom.toprettyxml(indent=" ", encoding="ascii"))
|
||||
|
||||
# For .docx files, suggest an RSID for tracked changes
|
||||
if input_file.endswith(".docx"):
|
||||
suggested_rsid = "".join(random.choices("0123456789ABCDEF", k=8))
|
||||
print(f"Suggested RSID for edit session: {suggested_rsid}")
|
||||
69
skills/docx/ooxml/scripts/validate.py
Executable file
69
skills/docx/ooxml/scripts/validate.py
Executable file
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Command line tool to validate Office document XML files against XSD schemas and tracked changes.
|
||||
|
||||
Usage:
|
||||
python validate.py <dir> --original <original_file>
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from validation import DOCXSchemaValidator, PPTXSchemaValidator, RedliningValidator
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Validate Office document XML files")
|
||||
parser.add_argument(
|
||||
"unpacked_dir",
|
||||
help="Path to unpacked Office document directory",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--original",
|
||||
required=True,
|
||||
help="Path to original file (.docx/.pptx/.xlsx)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-v",
|
||||
"--verbose",
|
||||
action="store_true",
|
||||
help="Enable verbose output",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
# Validate paths
|
||||
unpacked_dir = Path(args.unpacked_dir)
|
||||
original_file = Path(args.original)
|
||||
file_extension = original_file.suffix.lower()
|
||||
assert unpacked_dir.is_dir(), f"Error: {unpacked_dir} is not a directory"
|
||||
assert original_file.is_file(), f"Error: {original_file} is not a file"
|
||||
assert file_extension in [".docx", ".pptx", ".xlsx"], (
|
||||
f"Error: {original_file} must be a .docx, .pptx, or .xlsx file"
|
||||
)
|
||||
|
||||
# Run validations
|
||||
match file_extension:
|
||||
case ".docx":
|
||||
validators = [DOCXSchemaValidator, RedliningValidator]
|
||||
case ".pptx":
|
||||
validators = [PPTXSchemaValidator]
|
||||
case _:
|
||||
print(f"Error: Validation not supported for file type {file_extension}")
|
||||
sys.exit(1)
|
||||
|
||||
# Run validators
|
||||
success = True
|
||||
for V in validators:
|
||||
validator = V(unpacked_dir, original_file, verbose=args.verbose)
|
||||
if not validator.validate():
|
||||
success = False
|
||||
|
||||
if success:
|
||||
print("All validations PASSED!")
|
||||
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
15
skills/docx/ooxml/scripts/validation/__init__.py
Executable file
15
skills/docx/ooxml/scripts/validation/__init__.py
Executable file
@@ -0,0 +1,15 @@
|
||||
"""
|
||||
Validation modules for Word document processing.
|
||||
"""
|
||||
|
||||
from .base import BaseSchemaValidator
|
||||
from .docx import DOCXSchemaValidator
|
||||
from .pptx import PPTXSchemaValidator
|
||||
from .redlining import RedliningValidator
|
||||
|
||||
__all__ = [
|
||||
"BaseSchemaValidator",
|
||||
"DOCXSchemaValidator",
|
||||
"PPTXSchemaValidator",
|
||||
"RedliningValidator",
|
||||
]
|
||||
951
skills/docx/ooxml/scripts/validation/base.py
Executable file
951
skills/docx/ooxml/scripts/validation/base.py
Executable file
@@ -0,0 +1,951 @@
|
||||
"""
|
||||
Base validator with common validation logic for document files.
|
||||
"""
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
import lxml.etree
|
||||
|
||||
|
||||
class BaseSchemaValidator:
|
||||
"""Base validator with common validation logic for document files."""
|
||||
|
||||
# Elements whose 'id' attributes must be unique within their file
|
||||
# Format: element_name -> (attribute_name, scope)
|
||||
# scope can be 'file' (unique within file) or 'global' (unique across all files)
|
||||
UNIQUE_ID_REQUIREMENTS = {
|
||||
# Word elements
|
||||
"comment": ("id", "file"), # Comment IDs in comments.xml
|
||||
"commentrangestart": ("id", "file"), # Must match comment IDs
|
||||
"commentrangeend": ("id", "file"), # Must match comment IDs
|
||||
"bookmarkstart": ("id", "file"), # Bookmark start IDs
|
||||
"bookmarkend": ("id", "file"), # Bookmark end IDs
|
||||
# Note: ins and del (track changes) can share IDs when part of same revision
|
||||
# PowerPoint elements
|
||||
"sldid": ("id", "file"), # Slide IDs in presentation.xml
|
||||
"sldmasterid": ("id", "global"), # Slide master IDs must be globally unique
|
||||
"sldlayoutid": ("id", "global"), # Slide layout IDs must be globally unique
|
||||
"cm": ("authorid", "file"), # Comment author IDs
|
||||
# Excel elements
|
||||
"sheet": ("sheetid", "file"), # Sheet IDs in workbook.xml
|
||||
"definedname": ("id", "file"), # Named range IDs
|
||||
# Drawing/Shape elements (all formats)
|
||||
"cxnsp": ("id", "file"), # Connection shape IDs
|
||||
"sp": ("id", "file"), # Shape IDs
|
||||
"pic": ("id", "file"), # Picture IDs
|
||||
"grpsp": ("id", "file"), # Group shape IDs
|
||||
}
|
||||
|
||||
# Mapping of element names to expected relationship types
|
||||
# Subclasses should override this with format-specific mappings
|
||||
ELEMENT_RELATIONSHIP_TYPES = {}
|
||||
|
||||
# Unified schema mappings for all Office document types
|
||||
SCHEMA_MAPPINGS = {
|
||||
# Document type specific schemas
|
||||
"word": "ISO-IEC29500-4_2016/wml.xsd", # Word documents
|
||||
"ppt": "ISO-IEC29500-4_2016/pml.xsd", # PowerPoint presentations
|
||||
"xl": "ISO-IEC29500-4_2016/sml.xsd", # Excel spreadsheets
|
||||
# Common file types
|
||||
"[Content_Types].xml": "ecma/fouth-edition/opc-contentTypes.xsd",
|
||||
"app.xml": "ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd",
|
||||
"core.xml": "ecma/fouth-edition/opc-coreProperties.xsd",
|
||||
"custom.xml": "ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd",
|
||||
".rels": "ecma/fouth-edition/opc-relationships.xsd",
|
||||
# Word-specific files
|
||||
"people.xml": "microsoft/wml-2012.xsd",
|
||||
"commentsIds.xml": "microsoft/wml-cid-2016.xsd",
|
||||
"commentsExtensible.xml": "microsoft/wml-cex-2018.xsd",
|
||||
"commentsExtended.xml": "microsoft/wml-2012.xsd",
|
||||
# Chart files (common across document types)
|
||||
"chart": "ISO-IEC29500-4_2016/dml-chart.xsd",
|
||||
# Theme files (common across document types)
|
||||
"theme": "ISO-IEC29500-4_2016/dml-main.xsd",
|
||||
# Drawing and media files
|
||||
"drawing": "ISO-IEC29500-4_2016/dml-main.xsd",
|
||||
}
|
||||
|
||||
# Unified namespace constants
|
||||
MC_NAMESPACE = "http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
XML_NAMESPACE = "http://www.w3.org/XML/1998/namespace"
|
||||
|
||||
# Common OOXML namespaces used across validators
|
||||
PACKAGE_RELATIONSHIPS_NAMESPACE = (
|
||||
"http://schemas.openxmlformats.org/package/2006/relationships"
|
||||
)
|
||||
OFFICE_RELATIONSHIPS_NAMESPACE = (
|
||||
"http://schemas.openxmlformats.org/officeDocument/2006/relationships"
|
||||
)
|
||||
CONTENT_TYPES_NAMESPACE = (
|
||||
"http://schemas.openxmlformats.org/package/2006/content-types"
|
||||
)
|
||||
|
||||
# Folders where we should clean ignorable namespaces
|
||||
MAIN_CONTENT_FOLDERS = {"word", "ppt", "xl"}
|
||||
|
||||
# All allowed OOXML namespaces (superset of all document types)
|
||||
OOXML_NAMESPACES = {
|
||||
"http://schemas.openxmlformats.org/officeDocument/2006/math",
|
||||
"http://schemas.openxmlformats.org/officeDocument/2006/relationships",
|
||||
"http://schemas.openxmlformats.org/schemaLibrary/2006/main",
|
||||
"http://schemas.openxmlformats.org/drawingml/2006/main",
|
||||
"http://schemas.openxmlformats.org/drawingml/2006/chart",
|
||||
"http://schemas.openxmlformats.org/drawingml/2006/chartDrawing",
|
||||
"http://schemas.openxmlformats.org/drawingml/2006/diagram",
|
||||
"http://schemas.openxmlformats.org/drawingml/2006/picture",
|
||||
"http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing",
|
||||
"http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing",
|
||||
"http://schemas.openxmlformats.org/wordprocessingml/2006/main",
|
||||
"http://schemas.openxmlformats.org/presentationml/2006/main",
|
||||
"http://schemas.openxmlformats.org/spreadsheetml/2006/main",
|
||||
"http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes",
|
||||
"http://www.w3.org/XML/1998/namespace",
|
||||
}
|
||||
|
||||
def __init__(self, unpacked_dir, original_file, verbose=False):
|
||||
self.unpacked_dir = Path(unpacked_dir).resolve()
|
||||
self.original_file = Path(original_file)
|
||||
self.verbose = verbose
|
||||
|
||||
# Set schemas directory
|
||||
self.schemas_dir = Path(__file__).parent.parent.parent / "schemas"
|
||||
|
||||
# Get all XML and .rels files
|
||||
patterns = ["*.xml", "*.rels"]
|
||||
self.xml_files = [
|
||||
f for pattern in patterns for f in self.unpacked_dir.rglob(pattern)
|
||||
]
|
||||
|
||||
if not self.xml_files:
|
||||
print(f"Warning: No XML files found in {self.unpacked_dir}")
|
||||
|
||||
def validate(self):
|
||||
"""Run all validation checks and return True if all pass."""
|
||||
raise NotImplementedError("Subclasses must implement the validate method")
|
||||
|
||||
def validate_xml(self):
|
||||
"""Validate that all XML files are well-formed."""
|
||||
errors = []
|
||||
|
||||
for xml_file in self.xml_files:
|
||||
try:
|
||||
# Try to parse the XML file
|
||||
lxml.etree.parse(str(xml_file))
|
||||
except lxml.etree.XMLSyntaxError as e:
|
||||
errors.append(
|
||||
f" {xml_file.relative_to(self.unpacked_dir)}: "
|
||||
f"Line {e.lineno}: {e.msg}"
|
||||
)
|
||||
except Exception as e:
|
||||
errors.append(
|
||||
f" {xml_file.relative_to(self.unpacked_dir)}: "
|
||||
f"Unexpected error: {str(e)}"
|
||||
)
|
||||
|
||||
if errors:
|
||||
print(f"FAILED - Found {len(errors)} XML violations:")
|
||||
for error in errors:
|
||||
print(error)
|
||||
return False
|
||||
else:
|
||||
if self.verbose:
|
||||
print("PASSED - All XML files are well-formed")
|
||||
return True
|
||||
|
||||
def validate_namespaces(self):
|
||||
"""Validate that namespace prefixes in Ignorable attributes are declared."""
|
||||
errors = []
|
||||
|
||||
for xml_file in self.xml_files:
|
||||
try:
|
||||
root = lxml.etree.parse(str(xml_file)).getroot()
|
||||
declared = set(root.nsmap.keys()) - {None} # Exclude default namespace
|
||||
|
||||
for attr_val in [
|
||||
v for k, v in root.attrib.items() if k.endswith("Ignorable")
|
||||
]:
|
||||
undeclared = set(attr_val.split()) - declared
|
||||
errors.extend(
|
||||
f" {xml_file.relative_to(self.unpacked_dir)}: "
|
||||
f"Namespace '{ns}' in Ignorable but not declared"
|
||||
for ns in undeclared
|
||||
)
|
||||
except lxml.etree.XMLSyntaxError:
|
||||
continue
|
||||
|
||||
if errors:
|
||||
print(f"FAILED - {len(errors)} namespace issues:")
|
||||
for error in errors:
|
||||
print(error)
|
||||
return False
|
||||
if self.verbose:
|
||||
print("PASSED - All namespace prefixes properly declared")
|
||||
return True
|
||||
|
||||
def validate_unique_ids(self):
|
||||
"""Validate that specific IDs are unique according to OOXML requirements."""
|
||||
errors = []
|
||||
global_ids = {} # Track globally unique IDs across all files
|
||||
|
||||
for xml_file in self.xml_files:
|
||||
try:
|
||||
root = lxml.etree.parse(str(xml_file)).getroot()
|
||||
file_ids = {} # Track IDs that must be unique within this file
|
||||
|
||||
# Remove all mc:AlternateContent elements from the tree
|
||||
mc_elements = root.xpath(
|
||||
".//mc:AlternateContent", namespaces={"mc": self.MC_NAMESPACE}
|
||||
)
|
||||
for elem in mc_elements:
|
||||
elem.getparent().remove(elem)
|
||||
|
||||
# Now check IDs in the cleaned tree
|
||||
for elem in root.iter():
|
||||
# Get the element name without namespace
|
||||
tag = (
|
||||
elem.tag.split("}")[-1].lower()
|
||||
if "}" in elem.tag
|
||||
else elem.tag.lower()
|
||||
)
|
||||
|
||||
# Check if this element type has ID uniqueness requirements
|
||||
if tag in self.UNIQUE_ID_REQUIREMENTS:
|
||||
attr_name, scope = self.UNIQUE_ID_REQUIREMENTS[tag]
|
||||
|
||||
# Look for the specified attribute
|
||||
id_value = None
|
||||
for attr, value in elem.attrib.items():
|
||||
attr_local = (
|
||||
attr.split("}")[-1].lower()
|
||||
if "}" in attr
|
||||
else attr.lower()
|
||||
)
|
||||
if attr_local == attr_name:
|
||||
id_value = value
|
||||
break
|
||||
|
||||
if id_value is not None:
|
||||
if scope == "global":
|
||||
# Check global uniqueness
|
||||
if id_value in global_ids:
|
||||
prev_file, prev_line, prev_tag = global_ids[
|
||||
id_value
|
||||
]
|
||||
errors.append(
|
||||
f" {xml_file.relative_to(self.unpacked_dir)}: "
|
||||
f"Line {elem.sourceline}: Global ID '{id_value}' in <{tag}> "
|
||||
f"already used in {prev_file} at line {prev_line} in <{prev_tag}>"
|
||||
)
|
||||
else:
|
||||
global_ids[id_value] = (
|
||||
xml_file.relative_to(self.unpacked_dir),
|
||||
elem.sourceline,
|
||||
tag,
|
||||
)
|
||||
elif scope == "file":
|
||||
# Check file-level uniqueness
|
||||
key = (tag, attr_name)
|
||||
if key not in file_ids:
|
||||
file_ids[key] = {}
|
||||
|
||||
if id_value in file_ids[key]:
|
||||
prev_line = file_ids[key][id_value]
|
||||
errors.append(
|
||||
f" {xml_file.relative_to(self.unpacked_dir)}: "
|
||||
f"Line {elem.sourceline}: Duplicate {attr_name}='{id_value}' in <{tag}> "
|
||||
f"(first occurrence at line {prev_line})"
|
||||
)
|
||||
else:
|
||||
file_ids[key][id_value] = elem.sourceline
|
||||
|
||||
except (lxml.etree.XMLSyntaxError, Exception) as e:
|
||||
errors.append(
|
||||
f" {xml_file.relative_to(self.unpacked_dir)}: Error: {e}"
|
||||
)
|
||||
|
||||
if errors:
|
||||
print(f"FAILED - Found {len(errors)} ID uniqueness violations:")
|
||||
for error in errors:
|
||||
print(error)
|
||||
return False
|
||||
else:
|
||||
if self.verbose:
|
||||
print("PASSED - All required IDs are unique")
|
||||
return True
|
||||
|
||||
def validate_file_references(self):
|
||||
"""
|
||||
Validate that all .rels files properly reference files and that all files are referenced.
|
||||
"""
|
||||
errors = []
|
||||
|
||||
# Find all .rels files
|
||||
rels_files = list(self.unpacked_dir.rglob("*.rels"))
|
||||
|
||||
if not rels_files:
|
||||
if self.verbose:
|
||||
print("PASSED - No .rels files found")
|
||||
return True
|
||||
|
||||
# Get all files in the unpacked directory (excluding reference files)
|
||||
all_files = []
|
||||
for file_path in self.unpacked_dir.rglob("*"):
|
||||
if (
|
||||
file_path.is_file()
|
||||
and file_path.name != "[Content_Types].xml"
|
||||
and not file_path.name.endswith(".rels")
|
||||
): # This file is not referenced by .rels
|
||||
all_files.append(file_path.resolve())
|
||||
|
||||
# Track all files that are referenced by any .rels file
|
||||
all_referenced_files = set()
|
||||
|
||||
if self.verbose:
|
||||
print(
|
||||
f"Found {len(rels_files)} .rels files and {len(all_files)} target files"
|
||||
)
|
||||
|
||||
# Check each .rels file
|
||||
for rels_file in rels_files:
|
||||
try:
|
||||
# Parse relationships file
|
||||
rels_root = lxml.etree.parse(str(rels_file)).getroot()
|
||||
|
||||
# Get the directory where this .rels file is located
|
||||
rels_dir = rels_file.parent
|
||||
|
||||
# Find all relationships and their targets
|
||||
referenced_files = set()
|
||||
broken_refs = []
|
||||
|
||||
for rel in rels_root.findall(
|
||||
".//ns:Relationship",
|
||||
namespaces={"ns": self.PACKAGE_RELATIONSHIPS_NAMESPACE},
|
||||
):
|
||||
target = rel.get("Target")
|
||||
if target and not target.startswith(
|
||||
("http", "mailto:")
|
||||
): # Skip external URLs
|
||||
# Resolve the target path relative to the .rels file location
|
||||
if rels_file.name == ".rels":
|
||||
# Root .rels file - targets are relative to unpacked_dir
|
||||
target_path = self.unpacked_dir / target
|
||||
else:
|
||||
# Other .rels files - targets are relative to their parent's parent
|
||||
# e.g., word/_rels/document.xml.rels -> targets relative to word/
|
||||
base_dir = rels_dir.parent
|
||||
target_path = base_dir / target
|
||||
|
||||
# Normalize the path and check if it exists
|
||||
try:
|
||||
target_path = target_path.resolve()
|
||||
if target_path.exists() and target_path.is_file():
|
||||
referenced_files.add(target_path)
|
||||
all_referenced_files.add(target_path)
|
||||
else:
|
||||
broken_refs.append((target, rel.sourceline))
|
||||
except (OSError, ValueError):
|
||||
broken_refs.append((target, rel.sourceline))
|
||||
|
||||
# Report broken references
|
||||
if broken_refs:
|
||||
rel_path = rels_file.relative_to(self.unpacked_dir)
|
||||
for broken_ref, line_num in broken_refs:
|
||||
errors.append(
|
||||
f" {rel_path}: Line {line_num}: Broken reference to {broken_ref}"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
rel_path = rels_file.relative_to(self.unpacked_dir)
|
||||
errors.append(f" Error parsing {rel_path}: {e}")
|
||||
|
||||
# Check for unreferenced files (files that exist but are not referenced anywhere)
|
||||
unreferenced_files = set(all_files) - all_referenced_files
|
||||
|
||||
if unreferenced_files:
|
||||
for unref_file in sorted(unreferenced_files):
|
||||
unref_rel_path = unref_file.relative_to(self.unpacked_dir)
|
||||
errors.append(f" Unreferenced file: {unref_rel_path}")
|
||||
|
||||
if errors:
|
||||
print(f"FAILED - Found {len(errors)} relationship validation errors:")
|
||||
for error in errors:
|
||||
print(error)
|
||||
print(
|
||||
"CRITICAL: These errors will cause the document to appear corrupt. "
|
||||
+ "Broken references MUST be fixed, "
|
||||
+ "and unreferenced files MUST be referenced or removed."
|
||||
)
|
||||
return False
|
||||
else:
|
||||
if self.verbose:
|
||||
print(
|
||||
"PASSED - All references are valid and all files are properly referenced"
|
||||
)
|
||||
return True
|
||||
|
||||
def validate_all_relationship_ids(self):
|
||||
"""
|
||||
Validate that all r:id attributes in XML files reference existing IDs
|
||||
in their corresponding .rels files, and optionally validate relationship types.
|
||||
"""
|
||||
import lxml.etree
|
||||
|
||||
errors = []
|
||||
|
||||
# Process each XML file that might contain r:id references
|
||||
for xml_file in self.xml_files:
|
||||
# Skip .rels files themselves
|
||||
if xml_file.suffix == ".rels":
|
||||
continue
|
||||
|
||||
# Determine the corresponding .rels file
|
||||
# For dir/file.xml, it's dir/_rels/file.xml.rels
|
||||
rels_dir = xml_file.parent / "_rels"
|
||||
rels_file = rels_dir / f"{xml_file.name}.rels"
|
||||
|
||||
# Skip if there's no corresponding .rels file (that's okay)
|
||||
if not rels_file.exists():
|
||||
continue
|
||||
|
||||
try:
|
||||
# Parse the .rels file to get valid relationship IDs and their types
|
||||
rels_root = lxml.etree.parse(str(rels_file)).getroot()
|
||||
rid_to_type = {}
|
||||
|
||||
for rel in rels_root.findall(
|
||||
f".//{{{self.PACKAGE_RELATIONSHIPS_NAMESPACE}}}Relationship"
|
||||
):
|
||||
rid = rel.get("Id")
|
||||
rel_type = rel.get("Type", "")
|
||||
if rid:
|
||||
# Check for duplicate rIds
|
||||
if rid in rid_to_type:
|
||||
rels_rel_path = rels_file.relative_to(self.unpacked_dir)
|
||||
errors.append(
|
||||
f" {rels_rel_path}: Line {rel.sourceline}: "
|
||||
f"Duplicate relationship ID '{rid}' (IDs must be unique)"
|
||||
)
|
||||
# Extract just the type name from the full URL
|
||||
type_name = (
|
||||
rel_type.split("/")[-1] if "/" in rel_type else rel_type
|
||||
)
|
||||
rid_to_type[rid] = type_name
|
||||
|
||||
# Parse the XML file to find all r:id references
|
||||
xml_root = lxml.etree.parse(str(xml_file)).getroot()
|
||||
|
||||
# Find all elements with r:id attributes
|
||||
for elem in xml_root.iter():
|
||||
# Check for r:id attribute (relationship ID)
|
||||
rid_attr = elem.get(f"{{{self.OFFICE_RELATIONSHIPS_NAMESPACE}}}id")
|
||||
if rid_attr:
|
||||
xml_rel_path = xml_file.relative_to(self.unpacked_dir)
|
||||
elem_name = (
|
||||
elem.tag.split("}")[-1] if "}" in elem.tag else elem.tag
|
||||
)
|
||||
|
||||
# Check if the ID exists
|
||||
if rid_attr not in rid_to_type:
|
||||
errors.append(
|
||||
f" {xml_rel_path}: Line {elem.sourceline}: "
|
||||
f"<{elem_name}> references non-existent relationship '{rid_attr}' "
|
||||
f"(valid IDs: {', '.join(sorted(rid_to_type.keys())[:5])}{'...' if len(rid_to_type) > 5 else ''})"
|
||||
)
|
||||
# Check if we have type expectations for this element
|
||||
elif self.ELEMENT_RELATIONSHIP_TYPES:
|
||||
expected_type = self._get_expected_relationship_type(
|
||||
elem_name
|
||||
)
|
||||
if expected_type:
|
||||
actual_type = rid_to_type[rid_attr]
|
||||
# Check if the actual type matches or contains the expected type
|
||||
if expected_type not in actual_type.lower():
|
||||
errors.append(
|
||||
f" {xml_rel_path}: Line {elem.sourceline}: "
|
||||
f"<{elem_name}> references '{rid_attr}' which points to '{actual_type}' "
|
||||
f"but should point to a '{expected_type}' relationship"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
xml_rel_path = xml_file.relative_to(self.unpacked_dir)
|
||||
errors.append(f" Error processing {xml_rel_path}: {e}")
|
||||
|
||||
if errors:
|
||||
print(f"FAILED - Found {len(errors)} relationship ID reference errors:")
|
||||
for error in errors:
|
||||
print(error)
|
||||
print("\nThese ID mismatches will cause the document to appear corrupt!")
|
||||
return False
|
||||
else:
|
||||
if self.verbose:
|
||||
print("PASSED - All relationship ID references are valid")
|
||||
return True
|
||||
|
||||
def _get_expected_relationship_type(self, element_name):
|
||||
"""
|
||||
Get the expected relationship type for an element.
|
||||
First checks the explicit mapping, then tries pattern detection.
|
||||
"""
|
||||
# Normalize element name to lowercase
|
||||
elem_lower = element_name.lower()
|
||||
|
||||
# Check explicit mapping first
|
||||
if elem_lower in self.ELEMENT_RELATIONSHIP_TYPES:
|
||||
return self.ELEMENT_RELATIONSHIP_TYPES[elem_lower]
|
||||
|
||||
# Try pattern detection for common patterns
|
||||
# Pattern 1: Elements ending in "Id" often expect a relationship of the prefix type
|
||||
if elem_lower.endswith("id") and len(elem_lower) > 2:
|
||||
# e.g., "sldId" -> "sld", "sldMasterId" -> "sldMaster"
|
||||
prefix = elem_lower[:-2] # Remove "id"
|
||||
# Check if this might be a compound like "sldMasterId"
|
||||
if prefix.endswith("master"):
|
||||
return prefix.lower()
|
||||
elif prefix.endswith("layout"):
|
||||
return prefix.lower()
|
||||
else:
|
||||
# Simple case like "sldId" -> "slide"
|
||||
# Common transformations
|
||||
if prefix == "sld":
|
||||
return "slide"
|
||||
return prefix.lower()
|
||||
|
||||
# Pattern 2: Elements ending in "Reference" expect a relationship of the prefix type
|
||||
if elem_lower.endswith("reference") and len(elem_lower) > 9:
|
||||
prefix = elem_lower[:-9] # Remove "reference"
|
||||
return prefix.lower()
|
||||
|
||||
return None
|
||||
|
||||
def validate_content_types(self):
|
||||
"""Validate that all content files are properly declared in [Content_Types].xml."""
|
||||
errors = []
|
||||
|
||||
# Find [Content_Types].xml file
|
||||
content_types_file = self.unpacked_dir / "[Content_Types].xml"
|
||||
if not content_types_file.exists():
|
||||
print("FAILED - [Content_Types].xml file not found")
|
||||
return False
|
||||
|
||||
try:
|
||||
# Parse and get all declared parts and extensions
|
||||
root = lxml.etree.parse(str(content_types_file)).getroot()
|
||||
declared_parts = set()
|
||||
declared_extensions = set()
|
||||
|
||||
# Get Override declarations (specific files)
|
||||
for override in root.findall(
|
||||
f".//{{{self.CONTENT_TYPES_NAMESPACE}}}Override"
|
||||
):
|
||||
part_name = override.get("PartName")
|
||||
if part_name is not None:
|
||||
declared_parts.add(part_name.lstrip("/"))
|
||||
|
||||
# Get Default declarations (by extension)
|
||||
for default in root.findall(
|
||||
f".//{{{self.CONTENT_TYPES_NAMESPACE}}}Default"
|
||||
):
|
||||
extension = default.get("Extension")
|
||||
if extension is not None:
|
||||
declared_extensions.add(extension.lower())
|
||||
|
||||
# Root elements that require content type declaration
|
||||
declarable_roots = {
|
||||
"sld",
|
||||
"sldLayout",
|
||||
"sldMaster",
|
||||
"presentation", # PowerPoint
|
||||
"document", # Word
|
||||
"workbook",
|
||||
"worksheet", # Excel
|
||||
"theme", # Common
|
||||
}
|
||||
|
||||
# Common media file extensions that should be declared
|
||||
media_extensions = {
|
||||
"png": "image/png",
|
||||
"jpg": "image/jpeg",
|
||||
"jpeg": "image/jpeg",
|
||||
"gif": "image/gif",
|
||||
"bmp": "image/bmp",
|
||||
"tiff": "image/tiff",
|
||||
"wmf": "image/x-wmf",
|
||||
"emf": "image/x-emf",
|
||||
}
|
||||
|
||||
# Get all files in the unpacked directory
|
||||
all_files = list(self.unpacked_dir.rglob("*"))
|
||||
all_files = [f for f in all_files if f.is_file()]
|
||||
|
||||
# Check all XML files for Override declarations
|
||||
for xml_file in self.xml_files:
|
||||
path_str = str(xml_file.relative_to(self.unpacked_dir)).replace(
|
||||
"\\", "/"
|
||||
)
|
||||
|
||||
# Skip non-content files
|
||||
if any(
|
||||
skip in path_str
|
||||
for skip in [".rels", "[Content_Types]", "docProps/", "_rels/"]
|
||||
):
|
||||
continue
|
||||
|
||||
try:
|
||||
root_tag = lxml.etree.parse(str(xml_file)).getroot().tag
|
||||
root_name = root_tag.split("}")[-1] if "}" in root_tag else root_tag
|
||||
|
||||
if root_name in declarable_roots and path_str not in declared_parts:
|
||||
errors.append(
|
||||
f" {path_str}: File with <{root_name}> root not declared in [Content_Types].xml"
|
||||
)
|
||||
|
||||
except Exception:
|
||||
continue # Skip unparseable files
|
||||
|
||||
# Check all non-XML files for Default extension declarations
|
||||
for file_path in all_files:
|
||||
# Skip XML files and metadata files (already checked above)
|
||||
if file_path.suffix.lower() in {".xml", ".rels"}:
|
||||
continue
|
||||
if file_path.name == "[Content_Types].xml":
|
||||
continue
|
||||
if "_rels" in file_path.parts or "docProps" in file_path.parts:
|
||||
continue
|
||||
|
||||
extension = file_path.suffix.lstrip(".").lower()
|
||||
if extension and extension not in declared_extensions:
|
||||
# Check if it's a known media extension that should be declared
|
||||
if extension in media_extensions:
|
||||
relative_path = file_path.relative_to(self.unpacked_dir)
|
||||
errors.append(
|
||||
f' {relative_path}: File with extension \'{extension}\' not declared in [Content_Types].xml - should add: <Default Extension="{extension}" ContentType="{media_extensions[extension]}"/>'
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
errors.append(f" Error parsing [Content_Types].xml: {e}")
|
||||
|
||||
if errors:
|
||||
print(f"FAILED - Found {len(errors)} content type declaration errors:")
|
||||
for error in errors:
|
||||
print(error)
|
||||
return False
|
||||
else:
|
||||
if self.verbose:
|
||||
print(
|
||||
"PASSED - All content files are properly declared in [Content_Types].xml"
|
||||
)
|
||||
return True
|
||||
|
||||
def validate_file_against_xsd(self, xml_file, verbose=False):
|
||||
"""Validate a single XML file against XSD schema, comparing with original.
|
||||
|
||||
Args:
|
||||
xml_file: Path to XML file to validate
|
||||
verbose: Enable verbose output
|
||||
|
||||
Returns:
|
||||
tuple: (is_valid, new_errors_set) where is_valid is True/False/None (skipped)
|
||||
"""
|
||||
# Resolve both paths to handle symlinks
|
||||
xml_file = Path(xml_file).resolve()
|
||||
unpacked_dir = self.unpacked_dir.resolve()
|
||||
|
||||
# Validate current file
|
||||
is_valid, current_errors = self._validate_single_file_xsd(
|
||||
xml_file, unpacked_dir
|
||||
)
|
||||
|
||||
if is_valid is None:
|
||||
return None, set() # Skipped
|
||||
elif is_valid:
|
||||
return True, set() # Valid, no errors
|
||||
|
||||
# Get errors from original file for this specific file
|
||||
original_errors = self._get_original_file_errors(xml_file)
|
||||
|
||||
# Compare with original (both are guaranteed to be sets here)
|
||||
assert current_errors is not None
|
||||
new_errors = current_errors - original_errors
|
||||
|
||||
if new_errors:
|
||||
if verbose:
|
||||
relative_path = xml_file.relative_to(unpacked_dir)
|
||||
print(f"FAILED - {relative_path}: {len(new_errors)} new error(s)")
|
||||
for error in list(new_errors)[:3]:
|
||||
truncated = error[:250] + "..." if len(error) > 250 else error
|
||||
print(f" - {truncated}")
|
||||
return False, new_errors
|
||||
else:
|
||||
# All errors existed in original
|
||||
if verbose:
|
||||
print(
|
||||
f"PASSED - No new errors (original had {len(current_errors)} errors)"
|
||||
)
|
||||
return True, set()
|
||||
|
||||
def validate_against_xsd(self):
|
||||
"""Validate XML files against XSD schemas, showing only new errors compared to original."""
|
||||
new_errors = []
|
||||
original_error_count = 0
|
||||
valid_count = 0
|
||||
skipped_count = 0
|
||||
|
||||
for xml_file in self.xml_files:
|
||||
relative_path = str(xml_file.relative_to(self.unpacked_dir))
|
||||
is_valid, new_file_errors = self.validate_file_against_xsd(
|
||||
xml_file, verbose=False
|
||||
)
|
||||
|
||||
if is_valid is None:
|
||||
skipped_count += 1
|
||||
continue
|
||||
elif is_valid and not new_file_errors:
|
||||
valid_count += 1
|
||||
continue
|
||||
elif is_valid:
|
||||
# Had errors but all existed in original
|
||||
original_error_count += 1
|
||||
valid_count += 1
|
||||
continue
|
||||
|
||||
# Has new errors
|
||||
new_errors.append(f" {relative_path}: {len(new_file_errors)} new error(s)")
|
||||
for error in list(new_file_errors)[:3]: # Show first 3 errors
|
||||
new_errors.append(
|
||||
f" - {error[:250]}..." if len(error) > 250 else f" - {error}"
|
||||
)
|
||||
|
||||
# Print summary
|
||||
if self.verbose:
|
||||
print(f"Validated {len(self.xml_files)} files:")
|
||||
print(f" - Valid: {valid_count}")
|
||||
print(f" - Skipped (no schema): {skipped_count}")
|
||||
if original_error_count:
|
||||
print(f" - With original errors (ignored): {original_error_count}")
|
||||
print(
|
||||
f" - With NEW errors: {len(new_errors) > 0 and len([e for e in new_errors if not e.startswith(' ')]) or 0}"
|
||||
)
|
||||
|
||||
if new_errors:
|
||||
print("\nFAILED - Found NEW validation errors:")
|
||||
for error in new_errors:
|
||||
print(error)
|
||||
return False
|
||||
else:
|
||||
if self.verbose:
|
||||
print("\nPASSED - No new XSD validation errors introduced")
|
||||
return True
|
||||
|
||||
def _get_schema_path(self, xml_file):
|
||||
"""Determine the appropriate schema path for an XML file."""
|
||||
# Check exact filename match
|
||||
if xml_file.name in self.SCHEMA_MAPPINGS:
|
||||
return self.schemas_dir / self.SCHEMA_MAPPINGS[xml_file.name]
|
||||
|
||||
# Check .rels files
|
||||
if xml_file.suffix == ".rels":
|
||||
return self.schemas_dir / self.SCHEMA_MAPPINGS[".rels"]
|
||||
|
||||
# Check chart files
|
||||
if "charts/" in str(xml_file) and xml_file.name.startswith("chart"):
|
||||
return self.schemas_dir / self.SCHEMA_MAPPINGS["chart"]
|
||||
|
||||
# Check theme files
|
||||
if "theme/" in str(xml_file) and xml_file.name.startswith("theme"):
|
||||
return self.schemas_dir / self.SCHEMA_MAPPINGS["theme"]
|
||||
|
||||
# Check if file is in a main content folder and use appropriate schema
|
||||
if xml_file.parent.name in self.MAIN_CONTENT_FOLDERS:
|
||||
return self.schemas_dir / self.SCHEMA_MAPPINGS[xml_file.parent.name]
|
||||
|
||||
return None
|
||||
|
||||
def _clean_ignorable_namespaces(self, xml_doc):
|
||||
"""Remove attributes and elements not in allowed namespaces."""
|
||||
# Create a clean copy
|
||||
xml_string = lxml.etree.tostring(xml_doc, encoding="unicode")
|
||||
xml_copy = lxml.etree.fromstring(xml_string)
|
||||
|
||||
# Remove attributes not in allowed namespaces
|
||||
for elem in xml_copy.iter():
|
||||
attrs_to_remove = []
|
||||
|
||||
for attr in elem.attrib:
|
||||
# Check if attribute is from a namespace other than allowed ones
|
||||
if "{" in attr:
|
||||
ns = attr.split("}")[0][1:]
|
||||
if ns not in self.OOXML_NAMESPACES:
|
||||
attrs_to_remove.append(attr)
|
||||
|
||||
# Remove collected attributes
|
||||
for attr in attrs_to_remove:
|
||||
del elem.attrib[attr]
|
||||
|
||||
# Remove elements not in allowed namespaces
|
||||
self._remove_ignorable_elements(xml_copy)
|
||||
|
||||
return lxml.etree.ElementTree(xml_copy)
|
||||
|
||||
def _remove_ignorable_elements(self, root):
|
||||
"""Recursively remove all elements not in allowed namespaces."""
|
||||
elements_to_remove = []
|
||||
|
||||
# Find elements to remove
|
||||
for elem in list(root):
|
||||
# Skip non-element nodes (comments, processing instructions, etc.)
|
||||
if not hasattr(elem, "tag") or callable(elem.tag):
|
||||
continue
|
||||
|
||||
tag_str = str(elem.tag)
|
||||
if tag_str.startswith("{"):
|
||||
ns = tag_str.split("}")[0][1:]
|
||||
if ns not in self.OOXML_NAMESPACES:
|
||||
elements_to_remove.append(elem)
|
||||
continue
|
||||
|
||||
# Recursively clean child elements
|
||||
self._remove_ignorable_elements(elem)
|
||||
|
||||
# Remove collected elements
|
||||
for elem in elements_to_remove:
|
||||
root.remove(elem)
|
||||
|
||||
def _preprocess_for_mc_ignorable(self, xml_doc):
|
||||
"""Preprocess XML to handle mc:Ignorable attribute properly."""
|
||||
# Remove mc:Ignorable attributes before validation
|
||||
root = xml_doc.getroot()
|
||||
|
||||
# Remove mc:Ignorable attribute from root
|
||||
if f"{{{self.MC_NAMESPACE}}}Ignorable" in root.attrib:
|
||||
del root.attrib[f"{{{self.MC_NAMESPACE}}}Ignorable"]
|
||||
|
||||
return xml_doc
|
||||
|
||||
def _validate_single_file_xsd(self, xml_file, base_path):
|
||||
"""Validate a single XML file against XSD schema. Returns (is_valid, errors_set)."""
|
||||
schema_path = self._get_schema_path(xml_file)
|
||||
if not schema_path:
|
||||
return None, None # Skip file
|
||||
|
||||
try:
|
||||
# Load schema
|
||||
with open(schema_path, "rb") as xsd_file:
|
||||
parser = lxml.etree.XMLParser()
|
||||
xsd_doc = lxml.etree.parse(
|
||||
xsd_file, parser=parser, base_url=str(schema_path)
|
||||
)
|
||||
schema = lxml.etree.XMLSchema(xsd_doc)
|
||||
|
||||
# Load and preprocess XML
|
||||
with open(xml_file, "r") as f:
|
||||
xml_doc = lxml.etree.parse(f)
|
||||
|
||||
xml_doc, _ = self._remove_template_tags_from_text_nodes(xml_doc)
|
||||
xml_doc = self._preprocess_for_mc_ignorable(xml_doc)
|
||||
|
||||
# Clean ignorable namespaces if needed
|
||||
relative_path = xml_file.relative_to(base_path)
|
||||
if (
|
||||
relative_path.parts
|
||||
and relative_path.parts[0] in self.MAIN_CONTENT_FOLDERS
|
||||
):
|
||||
xml_doc = self._clean_ignorable_namespaces(xml_doc)
|
||||
|
||||
# Validate
|
||||
if schema.validate(xml_doc):
|
||||
return True, set()
|
||||
else:
|
||||
errors = set()
|
||||
for error in schema.error_log:
|
||||
# Store normalized error message (without line numbers for comparison)
|
||||
errors.add(error.message)
|
||||
return False, errors
|
||||
|
||||
except Exception as e:
|
||||
return False, {str(e)}
|
||||
|
||||
def _get_original_file_errors(self, xml_file):
|
||||
"""Get XSD validation errors from a single file in the original document.
|
||||
|
||||
Args:
|
||||
xml_file: Path to the XML file in unpacked_dir to check
|
||||
|
||||
Returns:
|
||||
set: Set of error messages from the original file
|
||||
"""
|
||||
import tempfile
|
||||
import zipfile
|
||||
|
||||
# Resolve both paths to handle symlinks (e.g., /var vs /private/var on macOS)
|
||||
xml_file = Path(xml_file).resolve()
|
||||
unpacked_dir = self.unpacked_dir.resolve()
|
||||
relative_path = xml_file.relative_to(unpacked_dir)
|
||||
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
temp_path = Path(temp_dir)
|
||||
|
||||
# Extract original file
|
||||
with zipfile.ZipFile(self.original_file, "r") as zip_ref:
|
||||
zip_ref.extractall(temp_path)
|
||||
|
||||
# Find corresponding file in original
|
||||
original_xml_file = temp_path / relative_path
|
||||
|
||||
if not original_xml_file.exists():
|
||||
# File didn't exist in original, so no original errors
|
||||
return set()
|
||||
|
||||
# Validate the specific file in original
|
||||
is_valid, errors = self._validate_single_file_xsd(
|
||||
original_xml_file, temp_path
|
||||
)
|
||||
return errors if errors else set()
|
||||
|
||||
def _remove_template_tags_from_text_nodes(self, xml_doc):
|
||||
"""Remove template tags from XML text nodes and collect warnings.
|
||||
|
||||
Template tags follow the pattern {{ ... }} and are used as placeholders
|
||||
for content replacement. They should be removed from text content before
|
||||
XSD validation while preserving XML structure.
|
||||
|
||||
Returns:
|
||||
tuple: (cleaned_xml_doc, warnings_list)
|
||||
"""
|
||||
warnings = []
|
||||
template_pattern = re.compile(r"\{\{[^}]*\}\}")
|
||||
|
||||
# Create a copy of the document to avoid modifying the original
|
||||
xml_string = lxml.etree.tostring(xml_doc, encoding="unicode")
|
||||
xml_copy = lxml.etree.fromstring(xml_string)
|
||||
|
||||
def process_text_content(text, content_type):
|
||||
if not text:
|
||||
return text
|
||||
matches = list(template_pattern.finditer(text))
|
||||
if matches:
|
||||
for match in matches:
|
||||
warnings.append(
|
||||
f"Found template tag in {content_type}: {match.group()}"
|
||||
)
|
||||
return template_pattern.sub("", text)
|
||||
return text
|
||||
|
||||
# Process all text nodes in the document
|
||||
for elem in xml_copy.iter():
|
||||
# Skip processing if this is a w:t element
|
||||
if not hasattr(elem, "tag") or callable(elem.tag):
|
||||
continue
|
||||
tag_str = str(elem.tag)
|
||||
if tag_str.endswith("}t") or tag_str == "t":
|
||||
continue
|
||||
|
||||
elem.text = process_text_content(elem.text, "text content")
|
||||
elem.tail = process_text_content(elem.tail, "tail content")
|
||||
|
||||
return lxml.etree.ElementTree(xml_copy), warnings
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise RuntimeError("This module should not be run directly.")
|
||||
274
skills/docx/ooxml/scripts/validation/docx.py
Executable file
274
skills/docx/ooxml/scripts/validation/docx.py
Executable file
@@ -0,0 +1,274 @@
|
||||
"""
|
||||
Validator for Word document XML files against XSD schemas.
|
||||
"""
|
||||
|
||||
import re
|
||||
import tempfile
|
||||
import zipfile
|
||||
|
||||
import lxml.etree
|
||||
|
||||
from .base import BaseSchemaValidator
|
||||
|
||||
|
||||
class DOCXSchemaValidator(BaseSchemaValidator):
|
||||
"""Validator for Word document XML files against XSD schemas."""
|
||||
|
||||
# Word-specific namespace
|
||||
WORD_2006_NAMESPACE = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
|
||||
|
||||
# Word-specific element to relationship type mappings
|
||||
# Start with empty mapping - add specific cases as we discover them
|
||||
ELEMENT_RELATIONSHIP_TYPES = {}
|
||||
|
||||
def validate(self):
|
||||
"""Run all validation checks and return True if all pass."""
|
||||
# Test 0: XML well-formedness
|
||||
if not self.validate_xml():
|
||||
return False
|
||||
|
||||
# Test 1: Namespace declarations
|
||||
all_valid = True
|
||||
if not self.validate_namespaces():
|
||||
all_valid = False
|
||||
|
||||
# Test 2: Unique IDs
|
||||
if not self.validate_unique_ids():
|
||||
all_valid = False
|
||||
|
||||
# Test 3: Relationship and file reference validation
|
||||
if not self.validate_file_references():
|
||||
all_valid = False
|
||||
|
||||
# Test 4: Content type declarations
|
||||
if not self.validate_content_types():
|
||||
all_valid = False
|
||||
|
||||
# Test 5: XSD schema validation
|
||||
if not self.validate_against_xsd():
|
||||
all_valid = False
|
||||
|
||||
# Test 6: Whitespace preservation
|
||||
if not self.validate_whitespace_preservation():
|
||||
all_valid = False
|
||||
|
||||
# Test 7: Deletion validation
|
||||
if not self.validate_deletions():
|
||||
all_valid = False
|
||||
|
||||
# Test 8: Insertion validation
|
||||
if not self.validate_insertions():
|
||||
all_valid = False
|
||||
|
||||
# Test 9: Relationship ID reference validation
|
||||
if not self.validate_all_relationship_ids():
|
||||
all_valid = False
|
||||
|
||||
# Count and compare paragraphs
|
||||
self.compare_paragraph_counts()
|
||||
|
||||
return all_valid
|
||||
|
||||
def validate_whitespace_preservation(self):
|
||||
"""
|
||||
Validate that w:t elements with whitespace have xml:space='preserve'.
|
||||
"""
|
||||
errors = []
|
||||
|
||||
for xml_file in self.xml_files:
|
||||
# Only check document.xml files
|
||||
if xml_file.name != "document.xml":
|
||||
continue
|
||||
|
||||
try:
|
||||
root = lxml.etree.parse(str(xml_file)).getroot()
|
||||
|
||||
# Find all w:t elements
|
||||
for elem in root.iter(f"{{{self.WORD_2006_NAMESPACE}}}t"):
|
||||
if elem.text:
|
||||
text = elem.text
|
||||
# Check if text starts or ends with whitespace
|
||||
if re.match(r"^\s.*", text) or re.match(r".*\s$", text):
|
||||
# Check if xml:space="preserve" attribute exists
|
||||
xml_space_attr = f"{{{self.XML_NAMESPACE}}}space"
|
||||
if (
|
||||
xml_space_attr not in elem.attrib
|
||||
or elem.attrib[xml_space_attr] != "preserve"
|
||||
):
|
||||
# Show a preview of the text
|
||||
text_preview = (
|
||||
repr(text)[:50] + "..."
|
||||
if len(repr(text)) > 50
|
||||
else repr(text)
|
||||
)
|
||||
errors.append(
|
||||
f" {xml_file.relative_to(self.unpacked_dir)}: "
|
||||
f"Line {elem.sourceline}: w:t element with whitespace missing xml:space='preserve': {text_preview}"
|
||||
)
|
||||
|
||||
except (lxml.etree.XMLSyntaxError, Exception) as e:
|
||||
errors.append(
|
||||
f" {xml_file.relative_to(self.unpacked_dir)}: Error: {e}"
|
||||
)
|
||||
|
||||
if errors:
|
||||
print(f"FAILED - Found {len(errors)} whitespace preservation violations:")
|
||||
for error in errors:
|
||||
print(error)
|
||||
return False
|
||||
else:
|
||||
if self.verbose:
|
||||
print("PASSED - All whitespace is properly preserved")
|
||||
return True
|
||||
|
||||
def validate_deletions(self):
|
||||
"""
|
||||
Validate that w:t elements are not within w:del elements.
|
||||
For some reason, XSD validation does not catch this, so we do it manually.
|
||||
"""
|
||||
errors = []
|
||||
|
||||
for xml_file in self.xml_files:
|
||||
# Only check document.xml files
|
||||
if xml_file.name != "document.xml":
|
||||
continue
|
||||
|
||||
try:
|
||||
root = lxml.etree.parse(str(xml_file)).getroot()
|
||||
|
||||
# Find all w:t elements that are descendants of w:del elements
|
||||
namespaces = {"w": self.WORD_2006_NAMESPACE}
|
||||
xpath_expression = ".//w:del//w:t"
|
||||
problematic_t_elements = root.xpath(
|
||||
xpath_expression, namespaces=namespaces
|
||||
)
|
||||
for t_elem in problematic_t_elements:
|
||||
if t_elem.text:
|
||||
# Show a preview of the text
|
||||
text_preview = (
|
||||
repr(t_elem.text)[:50] + "..."
|
||||
if len(repr(t_elem.text)) > 50
|
||||
else repr(t_elem.text)
|
||||
)
|
||||
errors.append(
|
||||
f" {xml_file.relative_to(self.unpacked_dir)}: "
|
||||
f"Line {t_elem.sourceline}: <w:t> found within <w:del>: {text_preview}"
|
||||
)
|
||||
|
||||
except (lxml.etree.XMLSyntaxError, Exception) as e:
|
||||
errors.append(
|
||||
f" {xml_file.relative_to(self.unpacked_dir)}: Error: {e}"
|
||||
)
|
||||
|
||||
if errors:
|
||||
print(f"FAILED - Found {len(errors)} deletion validation violations:")
|
||||
for error in errors:
|
||||
print(error)
|
||||
return False
|
||||
else:
|
||||
if self.verbose:
|
||||
print("PASSED - No w:t elements found within w:del elements")
|
||||
return True
|
||||
|
||||
def count_paragraphs_in_unpacked(self):
|
||||
"""Count the number of paragraphs in the unpacked document."""
|
||||
count = 0
|
||||
|
||||
for xml_file in self.xml_files:
|
||||
# Only check document.xml files
|
||||
if xml_file.name != "document.xml":
|
||||
continue
|
||||
|
||||
try:
|
||||
root = lxml.etree.parse(str(xml_file)).getroot()
|
||||
# Count all w:p elements
|
||||
paragraphs = root.findall(f".//{{{self.WORD_2006_NAMESPACE}}}p")
|
||||
count = len(paragraphs)
|
||||
except Exception as e:
|
||||
print(f"Error counting paragraphs in unpacked document: {e}")
|
||||
|
||||
return count
|
||||
|
||||
def count_paragraphs_in_original(self):
|
||||
"""Count the number of paragraphs in the original docx file."""
|
||||
count = 0
|
||||
|
||||
try:
|
||||
# Create temporary directory to unpack original
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
# Unpack original docx
|
||||
with zipfile.ZipFile(self.original_file, "r") as zip_ref:
|
||||
zip_ref.extractall(temp_dir)
|
||||
|
||||
# Parse document.xml
|
||||
doc_xml_path = temp_dir + "/word/document.xml"
|
||||
root = lxml.etree.parse(doc_xml_path).getroot()
|
||||
|
||||
# Count all w:p elements
|
||||
paragraphs = root.findall(f".//{{{self.WORD_2006_NAMESPACE}}}p")
|
||||
count = len(paragraphs)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error counting paragraphs in original document: {e}")
|
||||
|
||||
return count
|
||||
|
||||
def validate_insertions(self):
|
||||
"""
|
||||
Validate that w:delText elements are not within w:ins elements.
|
||||
w:delText is only allowed in w:ins if nested within a w:del.
|
||||
"""
|
||||
errors = []
|
||||
|
||||
for xml_file in self.xml_files:
|
||||
if xml_file.name != "document.xml":
|
||||
continue
|
||||
|
||||
try:
|
||||
root = lxml.etree.parse(str(xml_file)).getroot()
|
||||
namespaces = {"w": self.WORD_2006_NAMESPACE}
|
||||
|
||||
# Find w:delText in w:ins that are NOT within w:del
|
||||
invalid_elements = root.xpath(
|
||||
".//w:ins//w:delText[not(ancestor::w:del)]",
|
||||
namespaces=namespaces
|
||||
)
|
||||
|
||||
for elem in invalid_elements:
|
||||
text_preview = (
|
||||
repr(elem.text or "")[:50] + "..."
|
||||
if len(repr(elem.text or "")) > 50
|
||||
else repr(elem.text or "")
|
||||
)
|
||||
errors.append(
|
||||
f" {xml_file.relative_to(self.unpacked_dir)}: "
|
||||
f"Line {elem.sourceline}: <w:delText> within <w:ins>: {text_preview}"
|
||||
)
|
||||
|
||||
except (lxml.etree.XMLSyntaxError, Exception) as e:
|
||||
errors.append(
|
||||
f" {xml_file.relative_to(self.unpacked_dir)}: Error: {e}"
|
||||
)
|
||||
|
||||
if errors:
|
||||
print(f"FAILED - Found {len(errors)} insertion validation violations:")
|
||||
for error in errors:
|
||||
print(error)
|
||||
return False
|
||||
else:
|
||||
if self.verbose:
|
||||
print("PASSED - No w:delText elements within w:ins elements")
|
||||
return True
|
||||
|
||||
def compare_paragraph_counts(self):
|
||||
"""Compare paragraph counts between original and new document."""
|
||||
original_count = self.count_paragraphs_in_original()
|
||||
new_count = self.count_paragraphs_in_unpacked()
|
||||
|
||||
diff = new_count - original_count
|
||||
diff_str = f"+{diff}" if diff > 0 else str(diff)
|
||||
print(f"\nParagraphs: {original_count} → {new_count} ({diff_str})")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise RuntimeError("This module should not be run directly.")
|
||||
315
skills/docx/ooxml/scripts/validation/pptx.py
Executable file
315
skills/docx/ooxml/scripts/validation/pptx.py
Executable file
@@ -0,0 +1,315 @@
|
||||
"""
|
||||
Validator for PowerPoint presentation XML files against XSD schemas.
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from .base import BaseSchemaValidator
|
||||
|
||||
|
||||
class PPTXSchemaValidator(BaseSchemaValidator):
|
||||
"""Validator for PowerPoint presentation XML files against XSD schemas."""
|
||||
|
||||
# PowerPoint presentation namespace
|
||||
PRESENTATIONML_NAMESPACE = (
|
||||
"http://schemas.openxmlformats.org/presentationml/2006/main"
|
||||
)
|
||||
|
||||
# PowerPoint-specific element to relationship type mappings
|
||||
ELEMENT_RELATIONSHIP_TYPES = {
|
||||
"sldid": "slide",
|
||||
"sldmasterid": "slidemaster",
|
||||
"notesmasterid": "notesmaster",
|
||||
"sldlayoutid": "slidelayout",
|
||||
"themeid": "theme",
|
||||
"tablestyleid": "tablestyles",
|
||||
}
|
||||
|
||||
def validate(self):
|
||||
"""Run all validation checks and return True if all pass."""
|
||||
# Test 0: XML well-formedness
|
||||
if not self.validate_xml():
|
||||
return False
|
||||
|
||||
# Test 1: Namespace declarations
|
||||
all_valid = True
|
||||
if not self.validate_namespaces():
|
||||
all_valid = False
|
||||
|
||||
# Test 2: Unique IDs
|
||||
if not self.validate_unique_ids():
|
||||
all_valid = False
|
||||
|
||||
# Test 3: UUID ID validation
|
||||
if not self.validate_uuid_ids():
|
||||
all_valid = False
|
||||
|
||||
# Test 4: Relationship and file reference validation
|
||||
if not self.validate_file_references():
|
||||
all_valid = False
|
||||
|
||||
# Test 5: Slide layout ID validation
|
||||
if not self.validate_slide_layout_ids():
|
||||
all_valid = False
|
||||
|
||||
# Test 6: Content type declarations
|
||||
if not self.validate_content_types():
|
||||
all_valid = False
|
||||
|
||||
# Test 7: XSD schema validation
|
||||
if not self.validate_against_xsd():
|
||||
all_valid = False
|
||||
|
||||
# Test 8: Notes slide reference validation
|
||||
if not self.validate_notes_slide_references():
|
||||
all_valid = False
|
||||
|
||||
# Test 9: Relationship ID reference validation
|
||||
if not self.validate_all_relationship_ids():
|
||||
all_valid = False
|
||||
|
||||
# Test 10: Duplicate slide layout references validation
|
||||
if not self.validate_no_duplicate_slide_layouts():
|
||||
all_valid = False
|
||||
|
||||
return all_valid
|
||||
|
||||
def validate_uuid_ids(self):
|
||||
"""Validate that ID attributes that look like UUIDs contain only hex values."""
|
||||
import lxml.etree
|
||||
|
||||
errors = []
|
||||
# UUID pattern: 8-4-4-4-12 hex digits with optional braces/hyphens
|
||||
uuid_pattern = re.compile(
|
||||
r"^[\{\(]?[0-9A-Fa-f]{8}-?[0-9A-Fa-f]{4}-?[0-9A-Fa-f]{4}-?[0-9A-Fa-f]{4}-?[0-9A-Fa-f]{12}[\}\)]?$"
|
||||
)
|
||||
|
||||
for xml_file in self.xml_files:
|
||||
try:
|
||||
root = lxml.etree.parse(str(xml_file)).getroot()
|
||||
|
||||
# Check all elements for ID attributes
|
||||
for elem in root.iter():
|
||||
for attr, value in elem.attrib.items():
|
||||
# Check if this is an ID attribute
|
||||
attr_name = attr.split("}")[-1].lower()
|
||||
if attr_name == "id" or attr_name.endswith("id"):
|
||||
# Check if value looks like a UUID (has the right length and pattern structure)
|
||||
if self._looks_like_uuid(value):
|
||||
# Validate that it contains only hex characters in the right positions
|
||||
if not uuid_pattern.match(value):
|
||||
errors.append(
|
||||
f" {xml_file.relative_to(self.unpacked_dir)}: "
|
||||
f"Line {elem.sourceline}: ID '{value}' appears to be a UUID but contains invalid hex characters"
|
||||
)
|
||||
|
||||
except (lxml.etree.XMLSyntaxError, Exception) as e:
|
||||
errors.append(
|
||||
f" {xml_file.relative_to(self.unpacked_dir)}: Error: {e}"
|
||||
)
|
||||
|
||||
if errors:
|
||||
print(f"FAILED - Found {len(errors)} UUID ID validation errors:")
|
||||
for error in errors:
|
||||
print(error)
|
||||
return False
|
||||
else:
|
||||
if self.verbose:
|
||||
print("PASSED - All UUID-like IDs contain valid hex values")
|
||||
return True
|
||||
|
||||
def _looks_like_uuid(self, value):
|
||||
"""Check if a value has the general structure of a UUID."""
|
||||
# Remove common UUID delimiters
|
||||
clean_value = value.strip("{}()").replace("-", "")
|
||||
# Check if it's 32 hex-like characters (could include invalid hex chars)
|
||||
return len(clean_value) == 32 and all(c.isalnum() for c in clean_value)
|
||||
|
||||
def validate_slide_layout_ids(self):
|
||||
"""Validate that sldLayoutId elements in slide masters reference valid slide layouts."""
|
||||
import lxml.etree
|
||||
|
||||
errors = []
|
||||
|
||||
# Find all slide master files
|
||||
slide_masters = list(self.unpacked_dir.glob("ppt/slideMasters/*.xml"))
|
||||
|
||||
if not slide_masters:
|
||||
if self.verbose:
|
||||
print("PASSED - No slide masters found")
|
||||
return True
|
||||
|
||||
for slide_master in slide_masters:
|
||||
try:
|
||||
# Parse the slide master file
|
||||
root = lxml.etree.parse(str(slide_master)).getroot()
|
||||
|
||||
# Find the corresponding _rels file for this slide master
|
||||
rels_file = slide_master.parent / "_rels" / f"{slide_master.name}.rels"
|
||||
|
||||
if not rels_file.exists():
|
||||
errors.append(
|
||||
f" {slide_master.relative_to(self.unpacked_dir)}: "
|
||||
f"Missing relationships file: {rels_file.relative_to(self.unpacked_dir)}"
|
||||
)
|
||||
continue
|
||||
|
||||
# Parse the relationships file
|
||||
rels_root = lxml.etree.parse(str(rels_file)).getroot()
|
||||
|
||||
# Build a set of valid relationship IDs that point to slide layouts
|
||||
valid_layout_rids = set()
|
||||
for rel in rels_root.findall(
|
||||
f".//{{{self.PACKAGE_RELATIONSHIPS_NAMESPACE}}}Relationship"
|
||||
):
|
||||
rel_type = rel.get("Type", "")
|
||||
if "slideLayout" in rel_type:
|
||||
valid_layout_rids.add(rel.get("Id"))
|
||||
|
||||
# Find all sldLayoutId elements in the slide master
|
||||
for sld_layout_id in root.findall(
|
||||
f".//{{{self.PRESENTATIONML_NAMESPACE}}}sldLayoutId"
|
||||
):
|
||||
r_id = sld_layout_id.get(
|
||||
f"{{{self.OFFICE_RELATIONSHIPS_NAMESPACE}}}id"
|
||||
)
|
||||
layout_id = sld_layout_id.get("id")
|
||||
|
||||
if r_id and r_id not in valid_layout_rids:
|
||||
errors.append(
|
||||
f" {slide_master.relative_to(self.unpacked_dir)}: "
|
||||
f"Line {sld_layout_id.sourceline}: sldLayoutId with id='{layout_id}' "
|
||||
f"references r:id='{r_id}' which is not found in slide layout relationships"
|
||||
)
|
||||
|
||||
except (lxml.etree.XMLSyntaxError, Exception) as e:
|
||||
errors.append(
|
||||
f" {slide_master.relative_to(self.unpacked_dir)}: Error: {e}"
|
||||
)
|
||||
|
||||
if errors:
|
||||
print(f"FAILED - Found {len(errors)} slide layout ID validation errors:")
|
||||
for error in errors:
|
||||
print(error)
|
||||
print(
|
||||
"Remove invalid references or add missing slide layouts to the relationships file."
|
||||
)
|
||||
return False
|
||||
else:
|
||||
if self.verbose:
|
||||
print("PASSED - All slide layout IDs reference valid slide layouts")
|
||||
return True
|
||||
|
||||
def validate_no_duplicate_slide_layouts(self):
|
||||
"""Validate that each slide has exactly one slideLayout reference."""
|
||||
import lxml.etree
|
||||
|
||||
errors = []
|
||||
slide_rels_files = list(self.unpacked_dir.glob("ppt/slides/_rels/*.xml.rels"))
|
||||
|
||||
for rels_file in slide_rels_files:
|
||||
try:
|
||||
root = lxml.etree.parse(str(rels_file)).getroot()
|
||||
|
||||
# Find all slideLayout relationships
|
||||
layout_rels = [
|
||||
rel
|
||||
for rel in root.findall(
|
||||
f".//{{{self.PACKAGE_RELATIONSHIPS_NAMESPACE}}}Relationship"
|
||||
)
|
||||
if "slideLayout" in rel.get("Type", "")
|
||||
]
|
||||
|
||||
if len(layout_rels) > 1:
|
||||
errors.append(
|
||||
f" {rels_file.relative_to(self.unpacked_dir)}: has {len(layout_rels)} slideLayout references"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
errors.append(
|
||||
f" {rels_file.relative_to(self.unpacked_dir)}: Error: {e}"
|
||||
)
|
||||
|
||||
if errors:
|
||||
print("FAILED - Found slides with duplicate slideLayout references:")
|
||||
for error in errors:
|
||||
print(error)
|
||||
return False
|
||||
else:
|
||||
if self.verbose:
|
||||
print("PASSED - All slides have exactly one slideLayout reference")
|
||||
return True
|
||||
|
||||
def validate_notes_slide_references(self):
|
||||
"""Validate that each notesSlide file is referenced by only one slide."""
|
||||
import lxml.etree
|
||||
|
||||
errors = []
|
||||
notes_slide_references = {} # Track which slides reference each notesSlide
|
||||
|
||||
# Find all slide relationship files
|
||||
slide_rels_files = list(self.unpacked_dir.glob("ppt/slides/_rels/*.xml.rels"))
|
||||
|
||||
if not slide_rels_files:
|
||||
if self.verbose:
|
||||
print("PASSED - No slide relationship files found")
|
||||
return True
|
||||
|
||||
for rels_file in slide_rels_files:
|
||||
try:
|
||||
# Parse the relationships file
|
||||
root = lxml.etree.parse(str(rels_file)).getroot()
|
||||
|
||||
# Find all notesSlide relationships
|
||||
for rel in root.findall(
|
||||
f".//{{{self.PACKAGE_RELATIONSHIPS_NAMESPACE}}}Relationship"
|
||||
):
|
||||
rel_type = rel.get("Type", "")
|
||||
if "notesSlide" in rel_type:
|
||||
target = rel.get("Target", "")
|
||||
if target:
|
||||
# Normalize the target path to handle relative paths
|
||||
normalized_target = target.replace("../", "")
|
||||
|
||||
# Track which slide references this notesSlide
|
||||
slide_name = rels_file.stem.replace(
|
||||
".xml", ""
|
||||
) # e.g., "slide1"
|
||||
|
||||
if normalized_target not in notes_slide_references:
|
||||
notes_slide_references[normalized_target] = []
|
||||
notes_slide_references[normalized_target].append(
|
||||
(slide_name, rels_file)
|
||||
)
|
||||
|
||||
except (lxml.etree.XMLSyntaxError, Exception) as e:
|
||||
errors.append(
|
||||
f" {rels_file.relative_to(self.unpacked_dir)}: Error: {e}"
|
||||
)
|
||||
|
||||
# Check for duplicate references
|
||||
for target, references in notes_slide_references.items():
|
||||
if len(references) > 1:
|
||||
slide_names = [ref[0] for ref in references]
|
||||
errors.append(
|
||||
f" Notes slide '{target}' is referenced by multiple slides: {', '.join(slide_names)}"
|
||||
)
|
||||
for slide_name, rels_file in references:
|
||||
errors.append(f" - {rels_file.relative_to(self.unpacked_dir)}")
|
||||
|
||||
if errors:
|
||||
print(
|
||||
f"FAILED - Found {len([e for e in errors if not e.startswith(' ')])} notes slide reference validation errors:"
|
||||
)
|
||||
for error in errors:
|
||||
print(error)
|
||||
print("Each slide may optionally have its own slide file.")
|
||||
return False
|
||||
else:
|
||||
if self.verbose:
|
||||
print("PASSED - All notes slide references are unique")
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise RuntimeError("This module should not be run directly.")
|
||||
279
skills/docx/ooxml/scripts/validation/redlining.py
Executable file
279
skills/docx/ooxml/scripts/validation/redlining.py
Executable file
@@ -0,0 +1,279 @@
|
||||
"""
|
||||
Validator for tracked changes in Word documents.
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import tempfile
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class RedliningValidator:
|
||||
"""Validator for tracked changes in Word documents."""
|
||||
|
||||
def __init__(self, unpacked_dir, original_docx, verbose=False):
|
||||
self.unpacked_dir = Path(unpacked_dir)
|
||||
self.original_docx = Path(original_docx)
|
||||
self.verbose = verbose
|
||||
self.namespaces = {
|
||||
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
|
||||
}
|
||||
|
||||
def validate(self):
|
||||
"""Main validation method that returns True if valid, False otherwise."""
|
||||
# Verify unpacked directory exists and has correct structure
|
||||
modified_file = self.unpacked_dir / "word" / "document.xml"
|
||||
if not modified_file.exists():
|
||||
print(f"FAILED - Modified document.xml not found at {modified_file}")
|
||||
return False
|
||||
|
||||
# First, check if there are any tracked changes by GLM to validate
|
||||
try:
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
tree = ET.parse(modified_file)
|
||||
root = tree.getroot()
|
||||
|
||||
# Check for w:del or w:ins tags authored by GLM
|
||||
del_elements = root.findall(".//w:del", self.namespaces)
|
||||
ins_elements = root.findall(".//w:ins", self.namespaces)
|
||||
|
||||
# Filter to only include changes by GLM
|
||||
glm_del_elements = [
|
||||
elem
|
||||
for elem in del_elements
|
||||
if elem.get(f"{{{self.namespaces['w']}}}author") == "GLM"
|
||||
]
|
||||
glm_ins_elements = [
|
||||
elem
|
||||
for elem in ins_elements
|
||||
if elem.get(f"{{{self.namespaces['w']}}}author") == "GLM"
|
||||
]
|
||||
|
||||
# Redlining validation is only needed if tracked changes by GLM have been used.
|
||||
if not glm_del_elements and not glm_ins_elements:
|
||||
if self.verbose:
|
||||
print("PASSED - No tracked changes by GLM found.")
|
||||
return True
|
||||
|
||||
except Exception:
|
||||
# If we can't parse the XML, continue with full validation
|
||||
pass
|
||||
|
||||
# Create temporary directory for unpacking original docx
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
temp_path = Path(temp_dir)
|
||||
|
||||
# Unpack original docx
|
||||
try:
|
||||
with zipfile.ZipFile(self.original_docx, "r") as zip_ref:
|
||||
zip_ref.extractall(temp_path)
|
||||
except Exception as e:
|
||||
print(f"FAILED - Error unpacking original docx: {e}")
|
||||
return False
|
||||
|
||||
original_file = temp_path / "word" / "document.xml"
|
||||
if not original_file.exists():
|
||||
print(
|
||||
f"FAILED - Original document.xml not found in {self.original_docx}"
|
||||
)
|
||||
return False
|
||||
|
||||
# Parse both XML files using xml.etree.ElementTree for redlining validation
|
||||
try:
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
modified_tree = ET.parse(modified_file)
|
||||
modified_root = modified_tree.getroot()
|
||||
original_tree = ET.parse(original_file)
|
||||
original_root = original_tree.getroot()
|
||||
except ET.ParseError as e:
|
||||
print(f"FAILED - Error parsing XML files: {e}")
|
||||
return False
|
||||
|
||||
# Remove GLM's tracked changes from both documents
|
||||
self._remove_glm_tracked_changes(original_root)
|
||||
self._remove_glm_tracked_changes(modified_root)
|
||||
|
||||
# Extract and compare text content
|
||||
modified_text = self._extract_text_content(modified_root)
|
||||
original_text = self._extract_text_content(original_root)
|
||||
|
||||
if modified_text != original_text:
|
||||
# Show detailed character-level differences for each paragraph
|
||||
error_message = self._generate_detailed_diff(
|
||||
original_text, modified_text
|
||||
)
|
||||
print(error_message)
|
||||
return False
|
||||
|
||||
if self.verbose:
|
||||
print("PASSED - All changes by GLM are properly tracked")
|
||||
return True
|
||||
|
||||
def _generate_detailed_diff(self, original_text, modified_text):
|
||||
"""Generate detailed word-level differences using git word diff."""
|
||||
error_parts = [
|
||||
"FAILED - Document text doesn't match after removing GLM's tracked changes",
|
||||
"",
|
||||
"Likely causes:",
|
||||
" 1. Modified text inside another author's <w:ins> or <w:del> tags",
|
||||
" 2. Made edits without proper tracked changes",
|
||||
" 3. Didn't nest <w:del> inside <w:ins> when deleting another's insertion",
|
||||
"",
|
||||
"For pre-redlined documents, use correct patterns:",
|
||||
" - To reject another's INSERTION: Nest <w:del> inside their <w:ins>",
|
||||
" - To restore another's DELETION: Add new <w:ins> AFTER their <w:del>",
|
||||
"",
|
||||
]
|
||||
|
||||
# Show git word diff
|
||||
git_diff = self._get_git_word_diff(original_text, modified_text)
|
||||
if git_diff:
|
||||
error_parts.extend(["Differences:", "============", git_diff])
|
||||
else:
|
||||
error_parts.append("Unable to generate word diff (git not available)")
|
||||
|
||||
return "\n".join(error_parts)
|
||||
|
||||
def _get_git_word_diff(self, original_text, modified_text):
|
||||
"""Generate word diff using git with character-level precision."""
|
||||
try:
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
temp_path = Path(temp_dir)
|
||||
|
||||
# Create two files
|
||||
original_file = temp_path / "original.txt"
|
||||
modified_file = temp_path / "modified.txt"
|
||||
|
||||
original_file.write_text(original_text, encoding="utf-8")
|
||||
modified_file.write_text(modified_text, encoding="utf-8")
|
||||
|
||||
# Try character-level diff first for precise differences
|
||||
result = subprocess.run(
|
||||
[
|
||||
"git",
|
||||
"diff",
|
||||
"--word-diff=plain",
|
||||
"--word-diff-regex=.", # Character-by-character diff
|
||||
"-U0", # Zero lines of context - show only changed lines
|
||||
"--no-index",
|
||||
str(original_file),
|
||||
str(modified_file),
|
||||
],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
|
||||
if result.stdout.strip():
|
||||
# Clean up the output - remove git diff header lines
|
||||
lines = result.stdout.split("\n")
|
||||
# Skip the header lines (diff --git, index, +++, ---, @@)
|
||||
content_lines = []
|
||||
in_content = False
|
||||
for line in lines:
|
||||
if line.startswith("@@"):
|
||||
in_content = True
|
||||
continue
|
||||
if in_content and line.strip():
|
||||
content_lines.append(line)
|
||||
|
||||
if content_lines:
|
||||
return "\n".join(content_lines)
|
||||
|
||||
# Fallback to word-level diff if character-level is too verbose
|
||||
result = subprocess.run(
|
||||
[
|
||||
"git",
|
||||
"diff",
|
||||
"--word-diff=plain",
|
||||
"-U0", # Zero lines of context
|
||||
"--no-index",
|
||||
str(original_file),
|
||||
str(modified_file),
|
||||
],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
|
||||
if result.stdout.strip():
|
||||
lines = result.stdout.split("\n")
|
||||
content_lines = []
|
||||
in_content = False
|
||||
for line in lines:
|
||||
if line.startswith("@@"):
|
||||
in_content = True
|
||||
continue
|
||||
if in_content and line.strip():
|
||||
content_lines.append(line)
|
||||
return "\n".join(content_lines)
|
||||
|
||||
except (subprocess.CalledProcessError, FileNotFoundError, Exception):
|
||||
# Git not available or other error, return None to use fallback
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
def _remove_glm_tracked_changes(self, root):
|
||||
"""Remove tracked changes authored by GLM from the XML root."""
|
||||
ins_tag = f"{{{self.namespaces['w']}}}ins"
|
||||
del_tag = f"{{{self.namespaces['w']}}}del"
|
||||
author_attr = f"{{{self.namespaces['w']}}}author"
|
||||
|
||||
# Remove w:ins elements
|
||||
for parent in root.iter():
|
||||
to_remove = []
|
||||
for child in parent:
|
||||
if child.tag == ins_tag and child.get(author_attr) == "GLM":
|
||||
to_remove.append(child)
|
||||
for elem in to_remove:
|
||||
parent.remove(elem)
|
||||
|
||||
# Unwrap content in w:del elements where author is "GLM"
|
||||
deltext_tag = f"{{{self.namespaces['w']}}}delText"
|
||||
t_tag = f"{{{self.namespaces['w']}}}t"
|
||||
|
||||
for parent in root.iter():
|
||||
to_process = []
|
||||
for child in parent:
|
||||
if child.tag == del_tag and child.get(author_attr) == "GLM":
|
||||
to_process.append((child, list(parent).index(child)))
|
||||
|
||||
# Process in reverse order to maintain indices
|
||||
for del_elem, del_index in reversed(to_process):
|
||||
# Convert w:delText to w:t before moving
|
||||
for elem in del_elem.iter():
|
||||
if elem.tag == deltext_tag:
|
||||
elem.tag = t_tag
|
||||
|
||||
# Move all children of w:del to its parent before removing w:del
|
||||
for child in reversed(list(del_elem)):
|
||||
parent.insert(del_index, child)
|
||||
parent.remove(del_elem)
|
||||
|
||||
def _extract_text_content(self, root):
|
||||
"""Extract text content from Word XML, preserving paragraph structure.
|
||||
|
||||
Empty paragraphs are skipped to avoid false positives when tracked
|
||||
insertions add only structural elements without text content.
|
||||
"""
|
||||
p_tag = f"{{{self.namespaces['w']}}}p"
|
||||
t_tag = f"{{{self.namespaces['w']}}}t"
|
||||
|
||||
paragraphs = []
|
||||
for p_elem in root.findall(f".//{p_tag}"):
|
||||
# Get all text elements within this paragraph
|
||||
text_parts = []
|
||||
for t_elem in p_elem.findall(f".//{t_tag}"):
|
||||
if t_elem.text:
|
||||
text_parts.append(t_elem.text)
|
||||
paragraph_text = "".join(text_parts)
|
||||
# Skip empty paragraphs - they don't affect content validation
|
||||
if paragraph_text:
|
||||
paragraphs.append(paragraph_text)
|
||||
|
||||
return "\n".join(paragraphs)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise RuntimeError("This module should not be run directly.")
|
||||
1
skills/docx/scripts/__init__.py
Executable file
1
skills/docx/scripts/__init__.py
Executable file
@@ -0,0 +1 @@
|
||||
# Make scripts directory a package for relative imports in tests
|
||||
220
skills/docx/scripts/add_toc_placeholders.py
Executable file
220
skills/docx/scripts/add_toc_placeholders.py
Executable file
@@ -0,0 +1,220 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Add placeholder entries to Table of Contents in a DOCX file.
|
||||
|
||||
This script adds placeholder TOC entries between the 'separate' and 'end'
|
||||
field characters, so users see some content on first open instead of an empty TOC.
|
||||
The original file is replaced with the modified version.
|
||||
|
||||
Usage:
|
||||
python add_toc_placeholders.py <docx_file> --entries <entries_json>
|
||||
|
||||
entries_json format: JSON string with array of objects:
|
||||
[
|
||||
{"level": 1, "text": "Chapter 1 Overview", "page": "1"},
|
||||
{"level": 2, "text": "Section 1.1 Details", "page": "1"}
|
||||
]
|
||||
|
||||
If --entries is not provided, generates generic placeholders.
|
||||
|
||||
Example:
|
||||
python add_toc_placeholders.py document.docx
|
||||
python add_toc_placeholders.py document.docx --entries '[{"level":1,"text":"Introduction","page":"1"}]'
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import html
|
||||
import json
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def add_toc_placeholders(docx_path: str, entries: list = None) -> None:
|
||||
"""Add placeholder TOC entries to a DOCX file (in-place replacement).
|
||||
|
||||
Args:
|
||||
docx_path: Path to DOCX file (will be modified in-place)
|
||||
entries: Optional list of placeholder entries. Each entry should be a dict
|
||||
with 'level' (1-3), 'text', and 'page' keys.
|
||||
"""
|
||||
docx_path = Path(docx_path)
|
||||
|
||||
# Create temp directory for extraction
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
temp_path = Path(temp_dir)
|
||||
extracted_dir = temp_path / "extracted"
|
||||
temp_output = temp_path / "output.docx"
|
||||
|
||||
# Extract DOCX
|
||||
with zipfile.ZipFile(docx_path, 'r') as zip_ref:
|
||||
zip_ref.extractall(extracted_dir)
|
||||
|
||||
# Detect TOC styles from styles.xml
|
||||
toc_style_mapping = _detect_toc_styles(extracted_dir / "word" / "styles.xml")
|
||||
print(toc_style_mapping)
|
||||
# Process document.xml
|
||||
document_xml = extracted_dir / "word" / "document.xml"
|
||||
if not document_xml.exists():
|
||||
raise ValueError("document.xml not found in the DOCX file")
|
||||
|
||||
# Read and process XML
|
||||
content = document_xml.read_text(encoding='utf-8')
|
||||
|
||||
# Find TOC structure and add placeholders
|
||||
modified_content = _insert_toc_placeholders(content, entries, toc_style_mapping)
|
||||
|
||||
# Write back
|
||||
document_xml.write_text(modified_content, encoding='utf-8')
|
||||
|
||||
# Repack DOCX to temp file
|
||||
with zipfile.ZipFile(temp_output, 'w', zipfile.ZIP_DEFLATED) as zipf:
|
||||
for file_path in extracted_dir.rglob('*'):
|
||||
if file_path.is_file():
|
||||
arcname = file_path.relative_to(extracted_dir)
|
||||
zipf.write(file_path, arcname)
|
||||
|
||||
# Replace original file with modified version (use shutil.move for cross-device support)
|
||||
docx_path.unlink()
|
||||
shutil.move(str(temp_output), str(docx_path))
|
||||
|
||||
|
||||
def _detect_toc_styles(styles_xml_path: Path) -> dict:
|
||||
"""Detect TOC style IDs from styles.xml.
|
||||
|
||||
Args:
|
||||
styles_xml_path: Path to styles.xml
|
||||
|
||||
Returns:
|
||||
Dictionary mapping level (1, 2, 3) to style ID
|
||||
"""
|
||||
default_mapping = {1: "9", 2: "11", 3: "12"}
|
||||
|
||||
if not styles_xml_path.exists():
|
||||
return default_mapping
|
||||
|
||||
content = styles_xml_path.read_text(encoding='utf-8')
|
||||
|
||||
# Find styles with names like "toc 1", "toc 2", "toc 3"
|
||||
import re
|
||||
toc_styles = {}
|
||||
for match in re.finditer(r'<w:style[^>]*w:styleId="([^"]*)"[^>]*>.*?<w:name\s+w:val="toc\s+(\d)"', content, re.DOTALL):
|
||||
style_id = match.group(1)
|
||||
level = int(match.group(2))
|
||||
toc_styles[level] = style_id
|
||||
|
||||
# If we found styles, use them; otherwise use defaults
|
||||
return toc_styles if toc_styles else default_mapping
|
||||
|
||||
|
||||
def _insert_toc_placeholders(xml_content: str, entries: list = None, toc_style_mapping: dict = None) -> str:
|
||||
"""Insert placeholder TOC entries into XML content.
|
||||
|
||||
Args:
|
||||
xml_content: The XML content of document.xml
|
||||
entries: Optional list of placeholder entries
|
||||
toc_style_mapping: Dictionary mapping level to style ID
|
||||
|
||||
Returns:
|
||||
Modified XML content with placeholders inserted
|
||||
"""
|
||||
# Generate default placeholder entries if none provided
|
||||
if entries is None:
|
||||
entries = [
|
||||
{"level": 1, "text": "Chapter 1 Overview", "page": "1"},
|
||||
{"level": 2, "text": "Section 1.1 Details", "page": "1"},
|
||||
{"level": 2, "text": "Section 1.2 More Details", "page": "2"},
|
||||
{"level": 1, "text": "Chapter 2 Content", "page": "3"},
|
||||
]
|
||||
|
||||
# Use provided mapping or default
|
||||
if toc_style_mapping is None:
|
||||
toc_style_mapping = {1: "9", 2: "11", 3: "12"}
|
||||
|
||||
# Find the TOC structure: w:p with w:fldChar separate, followed by w:p with w:fldChar end
|
||||
# Pattern: <w:p><w:r>...<w:fldChar w:fldCharType="separate"/></w:r></w:p><w:p><w:r><w:fldChar w:fldCharType="end"/>
|
||||
separate_end_pattern = (
|
||||
r'(<w:p[^>]*><w:r[^>]*>.*?<w:fldChar[^>]*w:fldCharType="separate"[^>]*/></w:r></w:p>)'
|
||||
r'(<w:p[^>]*><w:r[^>]*>.*?<w:fldChar[^>]*w:fldCharType="end"[^>]*/></w:r></w:p>)'
|
||||
)
|
||||
|
||||
import re
|
||||
|
||||
def replace_with_placeholders(match):
|
||||
separate_para = match.group(1)
|
||||
end_para = match.group(2)
|
||||
|
||||
# Indentation values in twips (1 inch = 1440 twips)
|
||||
# Level 1: 0, Level 2: 0.25" (360), Level 3: 0.5" (720), Level 4+: 0.75" (1080)
|
||||
indent_mapping = {1: 0, 2: 360, 3: 720, 4: 1080, 5: 1440, 6: 1800}
|
||||
|
||||
# Generate placeholder paragraphs matching Word's TOC format
|
||||
placeholder_paragraphs = []
|
||||
for entry in entries:
|
||||
level = entry.get('level', 1)
|
||||
text = html.escape(entry.get('text', ''))
|
||||
page = entry.get('page', '1')
|
||||
|
||||
# Get style ID for this level
|
||||
toc_style = toc_style_mapping.get(level, toc_style_mapping.get(1, "9"))
|
||||
|
||||
# Get indentation for this level
|
||||
indent = indent_mapping.get(level, 0)
|
||||
indent_attr = f'<w:ind w:left="{indent}"/>' if indent > 0 else ''
|
||||
|
||||
# Use w:tab element (not w:tabStop) like Word does
|
||||
placeholder_para = f'''<w:p>
|
||||
<w:pPr>
|
||||
<w:pStyle w:val="{toc_style}"/>
|
||||
{indent_attr}
|
||||
<w:tabs><w:tab w:val="right" w:leader="dot" w:pos="9026"/></w:tabs>
|
||||
</w:pPr>
|
||||
<w:r><w:t>{text}</w:t></w:r>
|
||||
<w:r><w:tab/></w:r>
|
||||
<w:r><w:t>{page}</w:t></w:r>
|
||||
</w:p>'''
|
||||
placeholder_paragraphs.append(placeholder_para)
|
||||
|
||||
# Join with the separate paragraph at start and end paragraph at end
|
||||
return separate_para + '\n'.join(placeholder_paragraphs) + end_para
|
||||
|
||||
# Replace the pattern
|
||||
modified_content = re.sub(separate_end_pattern, replace_with_placeholders, xml_content, flags=re.DOTALL)
|
||||
|
||||
return modified_content
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Add placeholder entries to Table of Contents in a DOCX file (in-place)'
|
||||
)
|
||||
parser.add_argument('docx_file', help='DOCX file to modify (will be replaced)')
|
||||
parser.add_argument(
|
||||
'--entries',
|
||||
help='JSON string with placeholder entries: [{"level":1,"text":"Chapter 1","page":"1"}]'
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Parse entries if provided
|
||||
entries = None
|
||||
if args.entries:
|
||||
try:
|
||||
entries = json.loads(args.entries)
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"Error parsing entries JSON: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Add placeholders
|
||||
try:
|
||||
add_toc_placeholders(args.docx_file, entries)
|
||||
print(f"Successfully added TOC placeholders to {args.docx_file}")
|
||||
except Exception as e:
|
||||
print(f"Error: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
1302
skills/docx/scripts/document.py
Executable file
1302
skills/docx/scripts/document.py
Executable file
File diff suppressed because it is too large
Load Diff
3
skills/docx/scripts/templates/comments.xml
Executable file
3
skills/docx/scripts/templates/comments.xml
Executable file
@@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<w:comments xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:cx="http://schemas.microsoft.com/office/drawing/2014/chartex" xmlns:cx1="http://schemas.microsoft.com/office/drawing/2015/9/8/chartex" xmlns:cx2="http://schemas.microsoft.com/office/drawing/2015/10/21/chartex" xmlns:cx3="http://schemas.microsoft.com/office/drawing/2016/5/9/chartex" xmlns:cx4="http://schemas.microsoft.com/office/drawing/2016/5/10/chartex" xmlns:cx5="http://schemas.microsoft.com/office/drawing/2016/5/11/chartex" xmlns:cx6="http://schemas.microsoft.com/office/drawing/2016/5/12/chartex" xmlns:cx7="http://schemas.microsoft.com/office/drawing/2016/5/13/chartex" xmlns:cx8="http://schemas.microsoft.com/office/drawing/2016/5/14/chartex" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:aink="http://schemas.microsoft.com/office/drawing/2016/ink" xmlns:am3d="http://schemas.microsoft.com/office/drawing/2017/model3d" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:oel="http://schemas.microsoft.com/office/2019/extlst" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" xmlns:w16cex="http://schemas.microsoft.com/office/word/2018/wordml/cex" xmlns:w16cid="http://schemas.microsoft.com/office/word/2016/wordml/cid" xmlns:w16="http://schemas.microsoft.com/office/word/2018/wordml" xmlns:w16du="http://schemas.microsoft.com/office/word/2023/wordml/word16du" xmlns:w16sdtdh="http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash" xmlns:w16sdtfl="http://schemas.microsoft.com/office/word/2024/wordml/sdtformatlock" xmlns:w16se="http://schemas.microsoft.com/office/word/2015/wordml/symex" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" mc:Ignorable="w14 w15 w16se w16cid w16 w16cex w16sdtdh w16sdtfl w16du wp14">
|
||||
</w:comments>
|
||||
3
skills/docx/scripts/templates/commentsExtended.xml
Executable file
3
skills/docx/scripts/templates/commentsExtended.xml
Executable file
@@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<w15:commentsEx xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:cx="http://schemas.microsoft.com/office/drawing/2014/chartex" xmlns:cx1="http://schemas.microsoft.com/office/drawing/2015/9/8/chartex" xmlns:cx2="http://schemas.microsoft.com/office/drawing/2015/10/21/chartex" xmlns:cx3="http://schemas.microsoft.com/office/drawing/2016/5/9/chartex" xmlns:cx4="http://schemas.microsoft.com/office/drawing/2016/5/10/chartex" xmlns:cx5="http://schemas.microsoft.com/office/drawing/2016/5/11/chartex" xmlns:cx6="http://schemas.microsoft.com/office/drawing/2016/5/12/chartex" xmlns:cx7="http://schemas.microsoft.com/office/drawing/2016/5/13/chartex" xmlns:cx8="http://schemas.microsoft.com/office/drawing/2016/5/14/chartex" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:aink="http://schemas.microsoft.com/office/drawing/2016/ink" xmlns:am3d="http://schemas.microsoft.com/office/drawing/2017/model3d" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:oel="http://schemas.microsoft.com/office/2019/extlst" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" xmlns:w16cex="http://schemas.microsoft.com/office/word/2018/wordml/cex" xmlns:w16cid="http://schemas.microsoft.com/office/word/2016/wordml/cid" xmlns:w16="http://schemas.microsoft.com/office/word/2018/wordml" xmlns:w16du="http://schemas.microsoft.com/office/word/2023/wordml/word16du" xmlns:w16sdtdh="http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash" xmlns:w16sdtfl="http://schemas.microsoft.com/office/word/2024/wordml/sdtformatlock" xmlns:w16se="http://schemas.microsoft.com/office/word/2015/wordml/symex" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" mc:Ignorable="w14 w15 w16se w16cid w16 w16cex w16sdtdh w16sdtfl w16du wp14">
|
||||
</w15:commentsEx>
|
||||
3
skills/docx/scripts/templates/commentsExtensible.xml
Executable file
3
skills/docx/scripts/templates/commentsExtensible.xml
Executable file
@@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<w16cex:commentsExtensible xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:cx="http://schemas.microsoft.com/office/drawing/2014/chartex" xmlns:cx1="http://schemas.microsoft.com/office/drawing/2015/9/8/chartex" xmlns:cx2="http://schemas.microsoft.com/office/drawing/2015/10/21/chartex" xmlns:cx3="http://schemas.microsoft.com/office/drawing/2016/5/9/chartex" xmlns:cx4="http://schemas.microsoft.com/office/drawing/2016/5/10/chartex" xmlns:cx5="http://schemas.microsoft.com/office/drawing/2016/5/11/chartex" xmlns:cx6="http://schemas.microsoft.com/office/drawing/2016/5/12/chartex" xmlns:cx7="http://schemas.microsoft.com/office/drawing/2016/5/13/chartex" xmlns:cx8="http://schemas.microsoft.com/office/drawing/2016/5/14/chartex" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:aink="http://schemas.microsoft.com/office/drawing/2016/ink" xmlns:am3d="http://schemas.microsoft.com/office/drawing/2017/model3d" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:oel="http://schemas.microsoft.com/office/2019/extlst" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" xmlns:w16cex="http://schemas.microsoft.com/office/word/2018/wordml/cex" xmlns:w16cid="http://schemas.microsoft.com/office/word/2016/wordml/cid" xmlns:w16="http://schemas.microsoft.com/office/word/2018/wordml" xmlns:w16du="http://schemas.microsoft.com/office/word/2023/wordml/word16du" xmlns:w16sdtdh="http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash" xmlns:w16sdtfl="http://schemas.microsoft.com/office/word/2024/wordml/sdtformatlock" xmlns:w16se="http://schemas.microsoft.com/office/word/2015/wordml/symex" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" xmlns:cr="http://schemas.microsoft.com/office/comments/2020/reactions" mc:Ignorable="w14 w15 w16se w16cid w16 w16cex w16sdtdh w16sdtfl cr w16du wp14">
|
||||
</w16cex:commentsExtensible>
|
||||
3
skills/docx/scripts/templates/commentsIds.xml
Executable file
3
skills/docx/scripts/templates/commentsIds.xml
Executable file
@@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<w16cid:commentsIds xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:cx="http://schemas.microsoft.com/office/drawing/2014/chartex" xmlns:cx1="http://schemas.microsoft.com/office/drawing/2015/9/8/chartex" xmlns:cx2="http://schemas.microsoft.com/office/drawing/2015/10/21/chartex" xmlns:cx3="http://schemas.microsoft.com/office/drawing/2016/5/9/chartex" xmlns:cx4="http://schemas.microsoft.com/office/drawing/2016/5/10/chartex" xmlns:cx5="http://schemas.microsoft.com/office/drawing/2016/5/11/chartex" xmlns:cx6="http://schemas.microsoft.com/office/drawing/2016/5/12/chartex" xmlns:cx7="http://schemas.microsoft.com/office/drawing/2016/5/13/chartex" xmlns:cx8="http://schemas.microsoft.com/office/drawing/2016/5/14/chartex" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:aink="http://schemas.microsoft.com/office/drawing/2016/ink" xmlns:am3d="http://schemas.microsoft.com/office/drawing/2017/model3d" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:oel="http://schemas.microsoft.com/office/2019/extlst" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" xmlns:w16cex="http://schemas.microsoft.com/office/word/2018/wordml/cex" xmlns:w16cid="http://schemas.microsoft.com/office/word/2016/wordml/cid" xmlns:w16="http://schemas.microsoft.com/office/word/2018/wordml" xmlns:w16du="http://schemas.microsoft.com/office/word/2023/wordml/word16du" xmlns:w16sdtdh="http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash" xmlns:w16sdtfl="http://schemas.microsoft.com/office/word/2024/wordml/sdtformatlock" xmlns:w16se="http://schemas.microsoft.com/office/word/2015/wordml/symex" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" mc:Ignorable="w14 w15 w16se w16cid w16 w16cex w16sdtdh w16sdtfl w16du wp14">
|
||||
</w16cid:commentsIds>
|
||||
3
skills/docx/scripts/templates/people.xml
Executable file
3
skills/docx/scripts/templates/people.xml
Executable file
@@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<w15:people xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml">
|
||||
</w15:people>
|
||||
374
skills/docx/scripts/utilities.py
Executable file
374
skills/docx/scripts/utilities.py
Executable file
@@ -0,0 +1,374 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Utilities for editing OOXML documents.
|
||||
|
||||
This module provides XMLEditor, a tool for manipulating XML files with support for
|
||||
line-number-based node finding and DOM manipulation. Each element is automatically
|
||||
annotated with its original line and column position during parsing.
|
||||
|
||||
Example usage:
|
||||
editor = XMLEditor("document.xml")
|
||||
|
||||
# Find node by line number or range
|
||||
elem = editor.get_node(tag="w:r", line_number=519)
|
||||
elem = editor.get_node(tag="w:p", line_number=range(100, 200))
|
||||
|
||||
# Find node by text content
|
||||
elem = editor.get_node(tag="w:p", contains="specific text")
|
||||
|
||||
# Find node by attributes
|
||||
elem = editor.get_node(tag="w:r", attrs={"w:id": "target"})
|
||||
|
||||
# Combine filters
|
||||
elem = editor.get_node(tag="w:p", line_number=range(1, 50), contains="text")
|
||||
|
||||
# Replace, insert, or manipulate
|
||||
new_elem = editor.replace_node(elem, "<w:r><w:t>new text</w:t></w:r>")
|
||||
editor.insert_after(new_elem, "<w:r><w:t>more</w:t></w:r>")
|
||||
|
||||
# Save changes
|
||||
editor.save()
|
||||
"""
|
||||
|
||||
import html
|
||||
from pathlib import Path
|
||||
from typing import Optional, Union
|
||||
|
||||
import defusedxml.minidom
|
||||
import defusedxml.sax
|
||||
|
||||
|
||||
class XMLEditor:
|
||||
"""
|
||||
Editor for manipulating OOXML XML files with line-number-based node finding.
|
||||
|
||||
This class parses XML files and tracks the original line and column position
|
||||
of each element. This enables finding nodes by their line number in the original
|
||||
file, which is useful when working with Read tool output.
|
||||
|
||||
Attributes:
|
||||
xml_path: Path to the XML file being edited
|
||||
encoding: Detected encoding of the XML file ('ascii' or 'utf-8')
|
||||
dom: Parsed DOM tree with parse_position attributes on elements
|
||||
"""
|
||||
|
||||
def __init__(self, xml_path):
|
||||
"""
|
||||
Initialize with path to XML file and parse with line number tracking.
|
||||
|
||||
Args:
|
||||
xml_path: Path to XML file to edit (str or Path)
|
||||
|
||||
Raises:
|
||||
ValueError: If the XML file does not exist
|
||||
"""
|
||||
self.xml_path = Path(xml_path)
|
||||
if not self.xml_path.exists():
|
||||
raise ValueError(f"XML file not found: {xml_path}")
|
||||
|
||||
with open(self.xml_path, "rb") as f:
|
||||
header = f.read(200).decode("utf-8", errors="ignore")
|
||||
self.encoding = "ascii" if 'encoding="ascii"' in header else "utf-8"
|
||||
|
||||
parser = _create_line_tracking_parser()
|
||||
self.dom = defusedxml.minidom.parse(str(self.xml_path), parser)
|
||||
|
||||
def get_node(
|
||||
self,
|
||||
tag: str,
|
||||
attrs: Optional[dict[str, str]] = None,
|
||||
line_number: Optional[Union[int, range]] = None,
|
||||
contains: Optional[str] = None,
|
||||
):
|
||||
"""
|
||||
Get a DOM element by tag and identifier.
|
||||
|
||||
Finds an element by either its line number in the original file or by
|
||||
matching attribute values. Exactly one match must be found.
|
||||
|
||||
Args:
|
||||
tag: The XML tag name (e.g., "w:del", "w:ins", "w:r")
|
||||
attrs: Dictionary of attribute name-value pairs to match (e.g., {"w:id": "1"})
|
||||
line_number: Line number (int) or line range (range) in original XML file (1-indexed)
|
||||
contains: Text string that must appear in any text node within the element.
|
||||
Supports both entity notation (“) and Unicode characters (\u201c).
|
||||
|
||||
Returns:
|
||||
defusedxml.minidom.Element: The matching DOM element
|
||||
|
||||
Raises:
|
||||
ValueError: If node not found or multiple matches found
|
||||
|
||||
Example:
|
||||
elem = editor.get_node(tag="w:r", line_number=519)
|
||||
elem = editor.get_node(tag="w:r", line_number=range(100, 200))
|
||||
elem = editor.get_node(tag="w:del", attrs={"w:id": "1"})
|
||||
elem = editor.get_node(tag="w:p", attrs={"w14:paraId": "12345678"})
|
||||
elem = editor.get_node(tag="w:commentRangeStart", attrs={"w:id": "0"})
|
||||
elem = editor.get_node(tag="w:p", contains="specific text")
|
||||
elem = editor.get_node(tag="w:t", contains="“Agreement") # Entity notation
|
||||
elem = editor.get_node(tag="w:t", contains="\u201cAgreement") # Unicode character
|
||||
"""
|
||||
matches = []
|
||||
for elem in self.dom.getElementsByTagName(tag):
|
||||
# Check line_number filter
|
||||
if line_number is not None:
|
||||
parse_pos = getattr(elem, "parse_position", (None,))
|
||||
elem_line = parse_pos[0]
|
||||
|
||||
# Handle both single line number and range
|
||||
if isinstance(line_number, range):
|
||||
if elem_line not in line_number:
|
||||
continue
|
||||
else:
|
||||
if elem_line != line_number:
|
||||
continue
|
||||
|
||||
# Check attrs filter
|
||||
if attrs is not None:
|
||||
if not all(
|
||||
elem.getAttribute(attr_name) == attr_value
|
||||
for attr_name, attr_value in attrs.items()
|
||||
):
|
||||
continue
|
||||
|
||||
# Check contains filter
|
||||
if contains is not None:
|
||||
elem_text = self._get_element_text(elem)
|
||||
# Normalize the search string: convert HTML entities to Unicode characters
|
||||
# This allows searching for both "“Rowan" and ""Rowan"
|
||||
normalized_contains = html.unescape(contains)
|
||||
if normalized_contains not in elem_text:
|
||||
continue
|
||||
|
||||
# If all applicable filters passed, this is a match
|
||||
matches.append(elem)
|
||||
|
||||
if not matches:
|
||||
# Build descriptive error message
|
||||
filters = []
|
||||
if line_number is not None:
|
||||
line_str = (
|
||||
f"lines {line_number.start}-{line_number.stop - 1}"
|
||||
if isinstance(line_number, range)
|
||||
else f"line {line_number}"
|
||||
)
|
||||
filters.append(f"at {line_str}")
|
||||
if attrs is not None:
|
||||
filters.append(f"with attributes {attrs}")
|
||||
if contains is not None:
|
||||
filters.append(f"containing '{contains}'")
|
||||
|
||||
filter_desc = " ".join(filters) if filters else ""
|
||||
base_msg = f"Node not found: <{tag}> {filter_desc}".strip()
|
||||
|
||||
# Add helpful hint based on filters used
|
||||
if contains:
|
||||
hint = "Text may be split across elements or use different wording."
|
||||
elif line_number:
|
||||
hint = "Line numbers may have changed if document was modified."
|
||||
elif attrs:
|
||||
hint = "Verify attribute values are correct."
|
||||
else:
|
||||
hint = "Try adding filters (attrs, line_number, or contains)."
|
||||
|
||||
raise ValueError(f"{base_msg}. {hint}")
|
||||
if len(matches) > 1:
|
||||
raise ValueError(
|
||||
f"Multiple nodes found: <{tag}>. "
|
||||
f"Add more filters (attrs, line_number, or contains) to narrow the search."
|
||||
)
|
||||
return matches[0]
|
||||
|
||||
def _get_element_text(self, elem):
|
||||
"""
|
||||
Recursively extract all text content from an element.
|
||||
|
||||
Skips text nodes that contain only whitespace (spaces, tabs, newlines),
|
||||
which typically represent XML formatting rather than document content.
|
||||
|
||||
Args:
|
||||
elem: defusedxml.minidom.Element to extract text from
|
||||
|
||||
Returns:
|
||||
str: Concatenated text from all non-whitespace text nodes within the element
|
||||
"""
|
||||
text_parts = []
|
||||
for node in elem.childNodes:
|
||||
if node.nodeType == node.TEXT_NODE:
|
||||
# Skip whitespace-only text nodes (XML formatting)
|
||||
if node.data.strip():
|
||||
text_parts.append(node.data)
|
||||
elif node.nodeType == node.ELEMENT_NODE:
|
||||
text_parts.append(self._get_element_text(node))
|
||||
return "".join(text_parts)
|
||||
|
||||
def replace_node(self, elem, new_content):
|
||||
"""
|
||||
Replace a DOM element with new XML content.
|
||||
|
||||
Args:
|
||||
elem: defusedxml.minidom.Element to replace
|
||||
new_content: String containing XML to replace the node with
|
||||
|
||||
Returns:
|
||||
List[defusedxml.minidom.Node]: All inserted nodes
|
||||
|
||||
Example:
|
||||
new_nodes = editor.replace_node(old_elem, "<w:r><w:t>text</w:t></w:r>")
|
||||
"""
|
||||
parent = elem.parentNode
|
||||
nodes = self._parse_fragment(new_content)
|
||||
for node in nodes:
|
||||
parent.insertBefore(node, elem)
|
||||
parent.removeChild(elem)
|
||||
return nodes
|
||||
|
||||
def insert_after(self, elem, xml_content):
|
||||
"""
|
||||
Insert XML content after a DOM element.
|
||||
|
||||
Args:
|
||||
elem: defusedxml.minidom.Element to insert after
|
||||
xml_content: String containing XML to insert
|
||||
|
||||
Returns:
|
||||
List[defusedxml.minidom.Node]: All inserted nodes
|
||||
|
||||
Example:
|
||||
new_nodes = editor.insert_after(elem, "<w:r><w:t>text</w:t></w:r>")
|
||||
"""
|
||||
parent = elem.parentNode
|
||||
next_sibling = elem.nextSibling
|
||||
nodes = self._parse_fragment(xml_content)
|
||||
for node in nodes:
|
||||
if next_sibling:
|
||||
parent.insertBefore(node, next_sibling)
|
||||
else:
|
||||
parent.appendChild(node)
|
||||
return nodes
|
||||
|
||||
def insert_before(self, elem, xml_content):
|
||||
"""
|
||||
Insert XML content before a DOM element.
|
||||
|
||||
Args:
|
||||
elem: defusedxml.minidom.Element to insert before
|
||||
xml_content: String containing XML to insert
|
||||
|
||||
Returns:
|
||||
List[defusedxml.minidom.Node]: All inserted nodes
|
||||
|
||||
Example:
|
||||
new_nodes = editor.insert_before(elem, "<w:r><w:t>text</w:t></w:r>")
|
||||
"""
|
||||
parent = elem.parentNode
|
||||
nodes = self._parse_fragment(xml_content)
|
||||
for node in nodes:
|
||||
parent.insertBefore(node, elem)
|
||||
return nodes
|
||||
|
||||
def append_to(self, elem, xml_content):
|
||||
"""
|
||||
Append XML content as a child of a DOM element.
|
||||
|
||||
Args:
|
||||
elem: defusedxml.minidom.Element to append to
|
||||
xml_content: String containing XML to append
|
||||
|
||||
Returns:
|
||||
List[defusedxml.minidom.Node]: All inserted nodes
|
||||
|
||||
Example:
|
||||
new_nodes = editor.append_to(elem, "<w:r><w:t>text</w:t></w:r>")
|
||||
"""
|
||||
nodes = self._parse_fragment(xml_content)
|
||||
for node in nodes:
|
||||
elem.appendChild(node)
|
||||
return nodes
|
||||
|
||||
def get_next_rid(self):
|
||||
"""Get the next available rId for relationships files."""
|
||||
max_id = 0
|
||||
for rel_elem in self.dom.getElementsByTagName("Relationship"):
|
||||
rel_id = rel_elem.getAttribute("Id")
|
||||
if rel_id.startswith("rId"):
|
||||
try:
|
||||
max_id = max(max_id, int(rel_id[3:]))
|
||||
except ValueError:
|
||||
pass
|
||||
return f"rId{max_id + 1}"
|
||||
|
||||
def save(self):
|
||||
"""
|
||||
Save the edited XML back to the file.
|
||||
|
||||
Serializes the DOM tree and writes it back to the original file path,
|
||||
preserving the original encoding (ascii or utf-8).
|
||||
"""
|
||||
content = self.dom.toxml(encoding=self.encoding)
|
||||
self.xml_path.write_bytes(content)
|
||||
|
||||
def _parse_fragment(self, xml_content):
|
||||
"""
|
||||
Parse XML fragment and return list of imported nodes.
|
||||
|
||||
Args:
|
||||
xml_content: String containing XML fragment
|
||||
|
||||
Returns:
|
||||
List of defusedxml.minidom.Node objects imported into this document
|
||||
|
||||
Raises:
|
||||
AssertionError: If fragment contains no element nodes
|
||||
"""
|
||||
# Extract namespace declarations from the root document element
|
||||
root_elem = self.dom.documentElement
|
||||
namespaces = []
|
||||
if root_elem and root_elem.attributes:
|
||||
for i in range(root_elem.attributes.length):
|
||||
attr = root_elem.attributes.item(i)
|
||||
if attr.name.startswith("xmlns"): # type: ignore
|
||||
namespaces.append(f'{attr.name}="{attr.value}"') # type: ignore
|
||||
|
||||
ns_decl = " ".join(namespaces)
|
||||
wrapper = f"<root {ns_decl}>{xml_content}</root>"
|
||||
fragment_doc = defusedxml.minidom.parseString(wrapper)
|
||||
nodes = [
|
||||
self.dom.importNode(child, deep=True)
|
||||
for child in fragment_doc.documentElement.childNodes # type: ignore
|
||||
]
|
||||
elements = [n for n in nodes if n.nodeType == n.ELEMENT_NODE]
|
||||
assert elements, "Fragment must contain at least one element"
|
||||
return nodes
|
||||
|
||||
|
||||
def _create_line_tracking_parser():
|
||||
"""
|
||||
Create a SAX parser that tracks line and column numbers for each element.
|
||||
|
||||
Monkey patches the SAX content handler to store the current line and column
|
||||
position from the underlying expat parser onto each element as a parse_position
|
||||
attribute (line, column) tuple.
|
||||
|
||||
Returns:
|
||||
defusedxml.sax.xmlreader.XMLReader: Configured SAX parser
|
||||
"""
|
||||
|
||||
def set_content_handler(dom_handler):
|
||||
def startElementNS(name, tagName, attrs):
|
||||
orig_start_cb(name, tagName, attrs)
|
||||
cur_elem = dom_handler.elementStack[-1]
|
||||
cur_elem.parse_position = (
|
||||
parser._parser.CurrentLineNumber, # type: ignore
|
||||
parser._parser.CurrentColumnNumber, # type: ignore
|
||||
)
|
||||
|
||||
orig_start_cb = dom_handler.startElementNS
|
||||
dom_handler.startElementNS = startElementNS
|
||||
orig_set_content_handler(dom_handler)
|
||||
|
||||
parser = defusedxml.sax.make_parser()
|
||||
orig_set_content_handler = parser.setContentHandler
|
||||
parser.setContentHandler = set_content_handler # type: ignore
|
||||
return parser
|
||||
445
skills/finance/Finance_API_Doc.md
Executable file
445
skills/finance/Finance_API_Doc.md
Executable file
@@ -0,0 +1,445 @@
|
||||
# Finance API Complete Documentation
|
||||
|
||||
## API Overview
|
||||
|
||||
Finance API provides comprehensive financial data access interfaces, including real-time market data, historical stock prices, and the latest financial news.
|
||||
|
||||
### 🌐 Access via API Gateway
|
||||
|
||||
**This API is accessed through the web-dev-ai-gateway unified proxy service.**
|
||||
|
||||
**Gateway Configuration:**
|
||||
- **Gateway Base URL:** `GATEWAY_URL` (e.g., `https://internal-api.z.ai`)
|
||||
- **API Path Prefix:** `API_PREFIX` (e.g., `/external/finance`)
|
||||
- **Authentication:** Automatic (gateway injects `x-rapidapi-host` and `x-rapidapi-key`)
|
||||
- **Required Header:** `X-Z-AI-From: Z`
|
||||
|
||||
**URL Structure:**
|
||||
```
|
||||
{GATEWAY_URL}{API_PREFIX}/{endpoint}
|
||||
```
|
||||
|
||||
**Example:**
|
||||
- Full URL: `https://internal-api.z.ai/external/finance/v1/markets/search?search=Apple`
|
||||
- Breakdown:
|
||||
- `https://internal-api.z.ai` - Gateway base URL (`GATEWAY_URL`)
|
||||
- `/external/finance` - API path prefix (`API_PREFIX`)
|
||||
- `/v1/markets/search` - API endpoint path
|
||||
|
||||
|
||||
### Quick Start
|
||||
|
||||
```bash
|
||||
# Get real-time quote for Apple
|
||||
curl -X GET "{GATEWAY_URL}{API_PREFIX}/v1/markets/quote?ticker=AAPL&type=STOCKS" \
|
||||
-H "X-Z-AI-From: Z"
|
||||
```
|
||||
|
||||
|
||||
## 1. Market Data API
|
||||
|
||||
### 1.1 GET v2/markets/tickers - Get All Available Market Tickers
|
||||
|
||||
**Parameters:**
|
||||
- `page` (optional, Number): Page number, default value is 1
|
||||
- `type` (required, String): Asset type, optional values:
|
||||
- `STOCKS` - Stocks
|
||||
- `ETF` - Exchange Traded Funds
|
||||
- `MUTUALFUNDS` - Mutual Funds
|
||||
|
||||
**curl example (via Gateway):**
|
||||
```bash
|
||||
curl -X GET "{GATEWAY_URL}{API_PREFIX}/v2/markets/tickers?page=1&type=STOCKS" \
|
||||
-H "X-Z-AI-From: Z"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 1.2 GET v1/markets/search - Search Stocks
|
||||
|
||||
**Parameters:**
|
||||
- `search` (required, String): Search keyword (company name or stock symbol)
|
||||
|
||||
**curl example (via Gateway):**
|
||||
```bash
|
||||
curl -X GET "{GATEWAY_URL}{API_PREFIX}/v1/markets/search?search=Apple" \
|
||||
-H "X-Z-AI-From: Z"
|
||||
```
|
||||
|
||||
**Purpose:** Used to find specific stock or company ticker codes
|
||||
|
||||
---
|
||||
|
||||
### 1.3 GET v1/markets/quote (real-time) - Real-time Quotes
|
||||
|
||||
**Parameters:**
|
||||
- `ticker` (required, String): Stock symbol (only one can be entered)
|
||||
- `type` (required, String): Asset type
|
||||
- `STOCKS` - Stocks
|
||||
- `ETF` - Exchange Traded Funds
|
||||
- `MUTUALFUNDS` - Mutual Funds
|
||||
|
||||
**curl example (via Gateway):**
|
||||
```bash
|
||||
curl -X GET "{GATEWAY_URL}{API_PREFIX}/v1/markets/quote?ticker=AAPL&type=STOCKS" \
|
||||
-H "X-Z-AI-From: Z"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 1.4 GET v1/markets/stock/quotes (snapshots) - Snapshot Quotes
|
||||
|
||||
**Parameters:**
|
||||
- `ticker` (required, String): Stock symbols, separated by commas
|
||||
|
||||
**curl example:**
|
||||
```bash
|
||||
curl --request GET \
|
||||
--url '{GATEWAY_URL}{API_PREFIX}/v1/markets/stock/quotes?ticker=AAPL%2CMSFT%2C%5ESPX%2C%5ENYA%2CGAZP.ME%2CSIBN.ME%2CGEECEE.NS'
|
||||
```
|
||||
|
||||
**Purpose:** Batch get snapshot data for multiple stocks
|
||||
|
||||
---
|
||||
|
||||
|
||||
## 2. Historical Data API
|
||||
|
||||
### 2.1 GET v1/markets/stock/history - Stock Historical Data
|
||||
|
||||
**Parameters:**
|
||||
- `symbol` (required, String): Stock symbol
|
||||
- `interval` (required, String): Time interval
|
||||
- `5m` - 5 minutes
|
||||
- `15m` - 15 minutes
|
||||
- `30m` - 30 minutes
|
||||
- `1h` - 1 hour
|
||||
- `1d` - Daily
|
||||
- `1wk` - Weekly
|
||||
- `1mo` - Monthly
|
||||
- `3mo` - 3 months
|
||||
- `diffandsplits` (optional, String): Include dividend and split data
|
||||
- `true` - Include
|
||||
- `false` - Exclude (default)
|
||||
|
||||
**curl example:**
|
||||
```bash
|
||||
curl --request GET \
|
||||
--url '{GATEWAY_URL}{API_PREFIX}/v1/markets/stock/history?symbol=AAPL&interval=1d&diffandsplits=false'
|
||||
```
|
||||
|
||||
**Purpose:** Get historical price data for specific stocks, used for technical analysis and backtesting
|
||||
|
||||
---
|
||||
|
||||
### 2.2 GET v2/markets/stock/history - Stock Historical Data V2
|
||||
|
||||
**Parameters:**
|
||||
- `symbol` (required, String): Stock symbol
|
||||
- `interval` (optional, String): Time interval
|
||||
- `1m`, `2m`, `3m`, `4m`, `5m`, `15m`, `30m`
|
||||
- `1h`, `1d`, `1wk`, `1mo`, `1qty`
|
||||
- `limit` (optional, Number): Limit the number of candles (1-1000)
|
||||
- `dividend` (optional, String): Include dividend data (`true` or `false`)
|
||||
|
||||
**curl example:**
|
||||
```bash
|
||||
curl --request GET \
|
||||
--url '{GATEWAY_URL}{API_PREFIX}/v2/markets/stock/history?symbol=AAPL&interval=1m&limit=640'
|
||||
```
|
||||
|
||||
**Purpose:** Enhanced historical data interface
|
||||
|
||||
---
|
||||
|
||||
## 3. News API
|
||||
|
||||
### 3.1 GET v1/markets/news - Market News
|
||||
|
||||
**Parameters:**
|
||||
- `ticker` (optional, String): Stock symbols, comma-separated for multiple stocks
|
||||
|
||||
**curl example:**
|
||||
```bash
|
||||
# Get general market news
|
||||
curl --request GET \
|
||||
--url '{GATEWAY_URL}{API_PREFIX}/v1/markets/news'
|
||||
|
||||
# Get specific stock news
|
||||
curl --request GET \
|
||||
--url '{GATEWAY_URL}{API_PREFIX}/v1/markets/news?ticker=AAPL,TSLA'
|
||||
```
|
||||
|
||||
**Purpose:** Get the latest market news and updates
|
||||
|
||||
---
|
||||
|
||||
### 3.2 GET v2/markets/news - Market News V2
|
||||
|
||||
**Parameters:**
|
||||
- `ticker` (optional, String): Stock symbol
|
||||
- `type` (optional, String): News type (`ALL`, `VIDEO`, `PRESS-RELEASE`)
|
||||
|
||||
**curl example:**
|
||||
```bash
|
||||
curl --request GET \
|
||||
--url '{GATEWAY_URL}{API_PREFIX}/v2/markets/news?ticker=AAPL&type=ALL'
|
||||
```
|
||||
|
||||
**Purpose:** Enhanced interface for getting latest market-related news
|
||||
|
||||
---
|
||||
|
||||
## 5. Stock Detailed Information API
|
||||
|
||||
### 5.1 GET v1/markets/stock/modules (asset-profile) - Company Profile
|
||||
|
||||
**Parameters:**
|
||||
- `ticker` (required, String): Stock symbol
|
||||
|
||||
**curl example:**
|
||||
```bash
|
||||
curl --request GET \
|
||||
--url '{GATEWAY_URL}{API_PREFIX}/v1/markets/stock/modules?ticker=AAPL&module=asset-profile'
|
||||
```
|
||||
|
||||
**Purpose:** Get company basic information, business description, management team, etc.
|
||||
|
||||
---
|
||||
|
||||
### 5.2 GET v1/stock/modules - Stock Module Data
|
||||
|
||||
**Parameters:**
|
||||
- `ticker` (required, String): Stock symbol
|
||||
- `module` (required, String): Module name (one per request)
|
||||
- Acceptable values: `profile`, `income-statement`, `balance-sheet`, `cashflow-statement`,
|
||||
`statistics`, `calendar-events`, `sec-filings`, `recommendation-trend`,
|
||||
`upgrade-downgrade-history`, `institution-ownership`, `fund-ownership`,
|
||||
`major-directHolders`, `major-holders-breakdown`, `insider-transactions`,
|
||||
`insider-holders`, `net-share-purchase-activity`, `earnings`, `industry-trend`,
|
||||
`index-trend`, `sector-trend`
|
||||
|
||||
**curl example:**
|
||||
```bash
|
||||
# Get specific module
|
||||
curl --request GET \
|
||||
--url '{GATEWAY_URL}{API_PREFIX}/v1/markets/stock/modules?ticker=AAPL&module=statistics'
|
||||
```
|
||||
|
||||
**Purpose:** Get one data module per request (price, financial, analyst ratings, etc.)
|
||||
|
||||
---
|
||||
|
||||
### 5.3 GET v1/markets/stock/modules (statistics) - Stock Statistics
|
||||
|
||||
**Parameters:**
|
||||
- `ticker` (required, String): Stock symbol
|
||||
|
||||
**curl example:**
|
||||
```bash
|
||||
curl --request GET \
|
||||
--url '{GATEWAY_URL}{API_PREFIX}/v1/markets/stock/modules?ticker=AAPL&module=statistics'
|
||||
```
|
||||
|
||||
**Purpose:** Get key statistical indicators such as PE ratios, market cap, trading volume
|
||||
|
||||
---
|
||||
|
||||
### 5.4 GET v1/markets/stock/modules (financial-data) - Get Financial Data
|
||||
|
||||
**Parameters:**
|
||||
- `ticker` (required, String): Stock symbol
|
||||
- `module` (required, String): `financial-data`
|
||||
|
||||
**curl example:**
|
||||
```bash
|
||||
curl --request GET \
|
||||
--url '{GATEWAY_URL}{API_PREFIX}/v1/markets/stock/modules?ticker=AAPL&module=financial-data'
|
||||
```
|
||||
|
||||
**Purpose:** Get revenue, profit, cash flow and other financial indicators
|
||||
|
||||
---
|
||||
|
||||
### 5.5 GET v1/markets/stock/modules (sec-filings) - Get SEC Filings
|
||||
|
||||
**Parameters:**
|
||||
- `ticker` (required, String): Stock symbol
|
||||
- `module` (required, String): `sec-filings`
|
||||
|
||||
**curl example:**
|
||||
```bash
|
||||
curl --request GET \
|
||||
--url '{GATEWAY_URL}{API_PREFIX}/v1/markets/stock/modules?ticker=AAPL&module=sec-filings'
|
||||
```
|
||||
|
||||
**Purpose:** Get files submitted by companies to the U.S. Securities and Exchange Commission
|
||||
|
||||
---
|
||||
|
||||
### 5.6 GET v1/markets/stock/modules (earnings) - Earnings Data
|
||||
|
||||
**Parameters:**
|
||||
- `ticker` (required, String): Stock symbol
|
||||
|
||||
**curl example:**
|
||||
```bash
|
||||
curl --request GET \
|
||||
--url '{GATEWAY_URL}{API_PREFIX}/v1/markets/stock/modules?ticker=AAPL&module=earnings'
|
||||
```
|
||||
|
||||
**Purpose:** Get quarterly and annual earnings information
|
||||
|
||||
---
|
||||
|
||||
### 5.7 GET v1/markets/stock/modules (calendar-events) - Get Calendar Events
|
||||
|
||||
**Parameters:**
|
||||
- `ticker` (required, String): Stock symbol
|
||||
- `module` (required, String): `calendar-events`
|
||||
|
||||
**curl example:**
|
||||
```bash
|
||||
curl --request GET \
|
||||
--url '{GATEWAY_URL}{API_PREFIX}/v1/markets/stock/modules?ticker=AAPL&module=calendar-events'
|
||||
```
|
||||
|
||||
**Purpose:** Get upcoming earnings release dates, dividend dates, etc.
|
||||
|
||||
---
|
||||
|
||||
## 6. Financial Statements API
|
||||
|
||||
### 7.1 GET v1/markets/stock/modules (balance-sheet) - Balance Sheet
|
||||
|
||||
**Parameters:**
|
||||
- `ticker` (required, String): Stock symbol
|
||||
|
||||
**curl example:**
|
||||
```bash
|
||||
curl --request GET \
|
||||
--url '{GATEWAY_URL}{API_PREFIX}/v1/markets/stock/modules?ticker=AAPL&module=balance-sheet'
|
||||
```
|
||||
|
||||
**Purpose:** Get company balance sheet data
|
||||
|
||||
---
|
||||
|
||||
### 7.3 GET v1/markets/stock/modules (income-statement) - Income Statement
|
||||
|
||||
**Parameters:**
|
||||
- `ticker` (required, String): Stock symbol
|
||||
|
||||
**curl example:**
|
||||
```bash
|
||||
curl --request GET \
|
||||
--url '{GATEWAY_URL}{API_PREFIX}/v1/markets/stock/modules?ticker=AAPL&module=income-statement'
|
||||
```
|
||||
|
||||
**Purpose:** Get company income statement data
|
||||
|
||||
---
|
||||
|
||||
### 7.4 GET v1/markets/stock/modules (cashflow-statement) - Cash Flow Statement
|
||||
|
||||
**Parameters:**
|
||||
- `ticker` (required, String): Stock symbol
|
||||
|
||||
**curl example:**
|
||||
```bash
|
||||
curl --request GET \
|
||||
--url '{GATEWAY_URL}{API_PREFIX}/v1/markets/stock/modules?ticker=AAPL&module=cashflow-statement'
|
||||
```
|
||||
|
||||
**Purpose:** Get company cash flow statement data
|
||||
|
||||
---
|
||||
|
||||
## Usage Flow Examples
|
||||
|
||||
### Example 1: Find and Get Real-time Stock Data
|
||||
|
||||
```bash
|
||||
# 1. Search company
|
||||
GET /v1/markets/search?search=Apple
|
||||
|
||||
# 2. Get real-time quote
|
||||
GET /v1/markets/quote?ticker=AAPL&type=STOCKS
|
||||
|
||||
# 3. Get detailed information
|
||||
GET /v1/markets/stock/modules?ticker=AAPL&module=asset-profile
|
||||
```
|
||||
|
||||
### Example 2: Analyze Stock Investment Value
|
||||
|
||||
```bash
|
||||
# 1. Get financial data
|
||||
GET /v1/markets/stock/modules?ticker=AAPL&module=financial-data
|
||||
|
||||
# 2. Get earnings data
|
||||
GET /v1/markets/stock/modules?ticker=AAPL&module=earnings
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Usage Tips
|
||||
|
||||
### 1. Batch Query Optimization
|
||||
```bash
|
||||
# Get data for multiple stocks at once (snapshots endpoint) via Gateway
|
||||
curl -X GET "{GATEWAY_URL}{API_PREFIX}/v1/markets/stock/quotes?ticker=AAPL,MSFT,GOOGL,AMZN,TSLA" \
|
||||
-H "X-Z-AI-From: Z"
|
||||
```
|
||||
|
||||
### 2. Time Range Query
|
||||
```bash
|
||||
# Get historical data with specific interval via Gateway
|
||||
curl -X GET "{GATEWAY_URL}{API_PREFIX}/v1/markets/stock/history?symbol=AAPL&interval=1d&diffandsplits=false" \
|
||||
-H "X-Z-AI-From: Z"
|
||||
```
|
||||
|
||||
### 3. Combined Query Example
|
||||
### 3. Combined Query Example
|
||||
|
||||
**Python example (via Gateway):**
|
||||
```python
|
||||
import requests
|
||||
|
||||
# Gateway automatically handles authentication
|
||||
headers = {
|
||||
'X-Z-AI-From': 'Z'
|
||||
}
|
||||
|
||||
gateway_url = '{GATEWAY_URL}{API_PREFIX}/v1'
|
||||
symbol = 'AAPL'
|
||||
|
||||
# Get real-time price
|
||||
quote = requests.get(f'{gateway_url}/markets/quote?ticker={symbol}&type=STOCKS', headers=headers)
|
||||
|
||||
# Get company profile
|
||||
profile = requests.get(f'{gateway_url}/markets/stock/modules?ticker={symbol}&module=asset-profile', headers=headers)
|
||||
|
||||
# Get financial data
|
||||
financials = requests.get(f'{gateway_url}/markets/stock/modules?ticker={symbol}&module=financial-data', headers=headers)
|
||||
```
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Gateway Usage
|
||||
|
||||
1. **Authentication Header** - Always include `X-Z-AI-From: Z` header
|
||||
|
||||
### API Usage
|
||||
|
||||
1. **Rate Limiting:** Pay attention to API call frequency limits to avoid being throttled
|
||||
2. **Error Handling:** Implement comprehensive error handling mechanisms
|
||||
3. **Data Caching:** Consider caching common requests to optimize performance
|
||||
4. **Batch Queries:** Use comma-separated symbols parameter to query multiple stocks at once
|
||||
5. **Timestamps:** Use Unix timestamps for historical data queries
|
||||
6. **Parameter Validation:** Validate all required parameters before sending requests
|
||||
7. **Response Parsing:** Implement robust JSON parsing and data validation
|
||||
|
||||
---
|
||||
53
skills/finance/SKILL.md
Executable file
53
skills/finance/SKILL.md
Executable file
@@ -0,0 +1,53 @@
|
||||
---
|
||||
name: finance
|
||||
description: "Comprehensive Finance API integration skill for real-time and historical financial data analysis, market research, and investment decision-making. Priority use cases: stock price queries, market data analysis, company financial information, portfolio tracking, market news retrieval, stock screening, technical analysis, and any financial market-related requests. This skill should be the primary choice for all Finance API interactions and financial data needs."
|
||||
---
|
||||
|
||||
# Finance Skill
|
||||
|
||||
## Core Capabilities
|
||||
|
||||
### Market Data Retrieval
|
||||
- Real-time quotes: current prices, market snapshots, trading volumes
|
||||
- Historical data: price history, dividends, splits, corporate actions
|
||||
- Market indices: major indices performance and constituents
|
||||
- Currency data: forex rates and cryptocurrency information
|
||||
|
||||
### Analysis Tools
|
||||
- Stock screening: filters by metrics, ratios, and technical indicators
|
||||
- Financial ratios: P/E, EPS, ROE, debt-to-equity, and other key metrics
|
||||
- Technical indicators: moving averages, RSI, MACD, chart patterns
|
||||
- Comparative analysis: sector and peer group comparisons
|
||||
|
||||
### Market Intelligence
|
||||
- Company information: business profiles, management teams, statements
|
||||
- Market news: earnings reports and market analysis
|
||||
- Insider trading: buy/sell activities and ownership changes
|
||||
- Options data: chain data, implied volatility, and statistics
|
||||
## API Overview
|
||||
|
||||
Finance API provides comprehensive financial data access interfaces, including real-time market data, historical stock prices, options data, insider trading, and the latest financial news.
|
||||
|
||||
Skills Path
|
||||
Skill Location: {project_path}/skills/finance
|
||||
|
||||
this skill is located at above path in your project.
|
||||
|
||||
Reference Docs: See {Skill Location}/Finance_API_Doc.md for a working example.
|
||||
|
||||
## Zhipu AI - Hong Kong IPO Information
|
||||
- **Stock Code**: 2513.HK
|
||||
- **Company Name (Chinese)**: 北京智谱华章科技股份有限公司
|
||||
- **Company Name (English)**: Knowledge Atlas Technology Joint Stock Company Limited
|
||||
Zhipu AI is a leading Chinese large language model company specializing in AI foundational model research and development.
|
||||
|
||||
### Best Practices for Zhipu AI Stock Research (One-Shot Success Guide)
|
||||
|
||||
**Search Strategy:**
|
||||
- ✅ Use full English company name: `search=Knowledge+Atlas`
|
||||
- ❌ Avoid: `search=Zhipu`, `search=02513.HK` (returns empty results)
|
||||
|
||||
** Important **
|
||||
always read `Finance_API_Doc.md` before use the API
|
||||
|
||||
|
||||
36
skills/frontend-design/.gitignore
vendored
Executable file
36
skills/frontend-design/.gitignore
vendored
Executable file
@@ -0,0 +1,36 @@
|
||||
# Dependencies
|
||||
node_modules/
|
||||
.pnp
|
||||
.pnp.js
|
||||
|
||||
# Testing
|
||||
coverage/
|
||||
*.lcov
|
||||
.nyc_output
|
||||
|
||||
# Production
|
||||
build/
|
||||
dist/
|
||||
out/
|
||||
|
||||
# Misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# IDEs
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# Environment
|
||||
.env
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
21
skills/frontend-design/LICENSE
Executable file
21
skills/frontend-design/LICENSE
Executable file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 z-ai platform
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
442
skills/frontend-design/OPTIMIZATION_SUMMARY.md
Executable file
442
skills/frontend-design/OPTIMIZATION_SUMMARY.md
Executable file
@@ -0,0 +1,442 @@
|
||||
# Frontend Design Skill - Optimization Summary
|
||||
|
||||
## 📊 Comparison: Original vs Optimized Version
|
||||
|
||||
### Original Document Focus
|
||||
- Heavily prescriptive approach
|
||||
- Emphasis on "no arbitrary values" (almost too rigid)
|
||||
- Style packs as main organizing principle
|
||||
- Prompt-template heavy
|
||||
- Less guidance on creative execution
|
||||
|
||||
### Optimized Version (v2.0) Improvements
|
||||
|
||||
#### 1. **Dual-Mode Thinking: System + Creativity**
|
||||
|
||||
**Original Issue**: Too focused on systematic constraints, could lead to generic outputs.
|
||||
|
||||
**Optimization**:
|
||||
```markdown
|
||||
Core Principles (Non-Negotiable)
|
||||
1. Dual-Mode Thinking: System + Creativity
|
||||
- Systematic Foundation: tokens, scales, states
|
||||
- Creative Execution: BOLD aesthetics, unique choices, avoid "AI slop"
|
||||
```
|
||||
|
||||
**Why Better**: Balances consistency with uniqueness. Prevents cookie-cutter designs while maintaining maintainability.
|
||||
|
||||
#### 2. **Enhanced Trigger Pattern Detection**
|
||||
|
||||
**Original**: Basic "when to use" section
|
||||
|
||||
**Optimization**:
|
||||
```markdown
|
||||
Trigger phrases:
|
||||
- "build a website/app/component"
|
||||
- "create a dashboard/landing page"
|
||||
- "design a UI for..."
|
||||
- "make it modern/clean/premium"
|
||||
- "style this with..."
|
||||
|
||||
DO NOT use for:
|
||||
- Backend API development
|
||||
- Pure logic/algorithm implementation
|
||||
```
|
||||
|
||||
**Why Better**: More precise activation, prevents skill misuse.
|
||||
|
||||
#### 3. **Complete Implementation Workflow**
|
||||
|
||||
**Original**: Scattered throughout document
|
||||
|
||||
**Optimization**:
|
||||
```markdown
|
||||
Phase 1: Design Analysis & Token Definition
|
||||
Phase 2: Component Development
|
||||
Phase 3: Page Assembly
|
||||
Phase 4: Quality Assurance
|
||||
```
|
||||
|
||||
**Why Better**: Clear step-by-step process, easier to follow.
|
||||
|
||||
#### 4. **Production-Ready Code Examples**
|
||||
|
||||
**Original**: Only had theoretical guidelines
|
||||
|
||||
**Optimization**: Added complete examples:
|
||||
- `examples/css/tokens.css` - 400+ lines of production tokens
|
||||
- `examples/css/components.css` - 600+ lines of components
|
||||
- `examples/typescript/design-tokens.ts` - Type-safe token system
|
||||
- `examples/typescript/sample-components.tsx` - 500+ lines of React components
|
||||
- `examples/typescript/theme-provider.tsx` - Complete theme system
|
||||
- `examples/typescript/utils.ts` - 30+ utility functions
|
||||
|
||||
**Why Better**: Developers can copy-paste and adapt immediately.
|
||||
|
||||
#### 5. **Enhanced Accessibility Guidance**
|
||||
|
||||
**Original**: Basic mentions of WCAG
|
||||
|
||||
**Optimization**:
|
||||
```markdown
|
||||
Accessibility as Constraint
|
||||
- Color Contrast: Run checker, WCAG AA minimum (4.5:1)
|
||||
- Keyboard Navigation: Tab order, focus indicators
|
||||
- ARIA & Semantics: Use semantic HTML first, ARIA when needed
|
||||
- Test with: Keyboard only, screen readers, reduced motion
|
||||
```
|
||||
|
||||
**Why Better**: Specific, actionable, testable.
|
||||
|
||||
#### 6. **Design Direction Templates**
|
||||
|
||||
**Original**: Had style packs but not well-organized
|
||||
|
||||
**Optimization**: 5 detailed templates:
|
||||
1. Minimal Premium SaaS (Most Universal)
|
||||
2. Bold Editorial
|
||||
3. Soft & Organic
|
||||
4. Dark Neon (Restrained)
|
||||
5. Playful & Colorful
|
||||
|
||||
Each with:
|
||||
- Visual specifications
|
||||
- Best use cases
|
||||
- Token mappings
|
||||
|
||||
**Why Better**: Easier to choose and execute with confidence.
|
||||
|
||||
#### 7. **TypeScript Integration**
|
||||
|
||||
**Original**: No TypeScript support
|
||||
|
||||
**Optimization**: Complete TypeScript support:
|
||||
- Type-safe token interfaces
|
||||
- Generic component props
|
||||
- Utility type guards
|
||||
- Theme type definitions
|
||||
|
||||
**Why Better**: Modern development standard, catches errors early.
|
||||
|
||||
#### 8. **Theme Management System**
|
||||
|
||||
**Original**: Basic dark mode mention
|
||||
|
||||
**Optimization**: Full theme provider with:
|
||||
- Light/Dark/System modes
|
||||
- localStorage persistence
|
||||
- System preference detection
|
||||
- Easy toggle components
|
||||
- HOC support
|
||||
|
||||
**Why Better**: Production-ready theme system out of the box.
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Key Optimizations Explained
|
||||
|
||||
### 1. Token System Enhancement
|
||||
|
||||
**Before**: Abstract token mentions
|
||||
**After**: Concrete implementation with OKLCH colors
|
||||
|
||||
```css
|
||||
/* Before: Vague */
|
||||
--primary: blue;
|
||||
|
||||
/* After: Precise, theme-aware, perceptually uniform */
|
||||
--primary: oklch(55% 0.18 250);
|
||||
--primary-hover: oklch(50% 0.20 250);
|
||||
--primary-active: oklch(45% 0.22 250);
|
||||
```
|
||||
|
||||
**Benefits**:
|
||||
- Perceptually uniform color adjustments
|
||||
- Easier dark mode (adjust lightness only)
|
||||
- Better color contrast control
|
||||
|
||||
### 2. Component State Coverage
|
||||
|
||||
**Before**: Mentioned but not enforced
|
||||
**After**: Mandatory checklist
|
||||
|
||||
```markdown
|
||||
For EVERY interactive element:
|
||||
✓ Default, Hover, Active, Focus, Disabled
|
||||
✓ Loading, Empty, Error
|
||||
|
||||
Missing states = incomplete implementation
|
||||
```
|
||||
|
||||
**Benefits**: No forgotten edge cases, better UX.
|
||||
|
||||
### 3. Fluid Typography
|
||||
|
||||
**Before**: Fixed sizes
|
||||
**After**: Responsive with clamp()
|
||||
|
||||
```css
|
||||
/* Before */
|
||||
--font-size-base: 16px;
|
||||
|
||||
/* After: Scales from mobile to desktop */
|
||||
--font-size-base: clamp(1rem, 0.95rem + 0.25vw, 1.125rem);
|
||||
```
|
||||
|
||||
**Benefits**: Better readability across devices, reduces media query complexity.
|
||||
|
||||
### 4. Advanced Motion Patterns
|
||||
|
||||
**Before**: Basic transitions
|
||||
**After**: Complete animation system
|
||||
|
||||
```css
|
||||
@keyframes shimmer {
|
||||
0% { background-position: 200% 0; }
|
||||
100% { background-position: -200% 0; }
|
||||
}
|
||||
|
||||
/* Respect reduced motion */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
* { animation-duration: 0.01ms !important; }
|
||||
}
|
||||
```
|
||||
|
||||
**Benefits**: Professional loading states, accessibility compliance.
|
||||
|
||||
### 5. Utility Functions
|
||||
|
||||
**Before**: None
|
||||
**After**: 30+ production utilities
|
||||
|
||||
Examples:
|
||||
```typescript
|
||||
cn(...classes) // Smart class merging
|
||||
debounce(fn, ms) // Performance optimization
|
||||
copyToClipboard(text) // UX enhancement
|
||||
formatRelativeTime(date) // Better dates
|
||||
prefersReducedMotion() // Accessibility check
|
||||
```
|
||||
|
||||
**Benefits**: Common patterns solved, less boilerplate.
|
||||
|
||||
---
|
||||
|
||||
## 📁 File Organization
|
||||
|
||||
```
|
||||
frontend-design/
|
||||
├── SKILL.md # 18,000+ words comprehensive guide
|
||||
├── README.md # Quick start (2,000 words)
|
||||
├── LICENSE # MIT
|
||||
├── package.json # Dependencies reference
|
||||
├── .gitignore # Standard ignores
|
||||
├── examples/
|
||||
│ ├── css/
|
||||
│ │ ├── tokens.css # 400+ lines design system
|
||||
│ │ └── components.css # 600+ lines components
|
||||
│ └── typescript/
|
||||
│ ├── design-tokens.ts # 350+ lines types
|
||||
│ ├── theme-provider.tsx # 250+ lines theme system
|
||||
│ ├── sample-components.tsx # 500+ lines components
|
||||
│ └── utils.ts # 400+ lines utilities
|
||||
└── templates/
|
||||
├── tailwind.config.js # 250+ lines configuration
|
||||
└── globals.css # 300+ lines global styles
|
||||
```
|
||||
|
||||
**Total**: ~4,000 lines of production-ready code
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Usage Examples Comparison
|
||||
|
||||
### Example 1: Button Component
|
||||
|
||||
**Before (Original doc)**:
|
||||
```
|
||||
User: Create a button
|
||||
AI: [Writes hardcoded button with inline styles]
|
||||
```
|
||||
|
||||
**After (Optimized)**:
|
||||
```typescript
|
||||
// Production-ready, type-safe, accessible
|
||||
<Button
|
||||
variant="primary"
|
||||
size="md"
|
||||
isLoading={isSubmitting}
|
||||
leftIcon={<CheckIcon />}
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
Save Changes
|
||||
</Button>
|
||||
|
||||
// Automatically includes:
|
||||
// - Hover/Focus/Active/Disabled states
|
||||
// - Loading spinner
|
||||
// - Keyboard accessibility
|
||||
// - Token-based styling
|
||||
// - TypeScript types
|
||||
```
|
||||
|
||||
### Example 2: Theme Toggle
|
||||
|
||||
**Before (Original doc)**:
|
||||
```
|
||||
User: Add dark mode
|
||||
AI: [Writes basic CSS dark mode, no state management]
|
||||
```
|
||||
|
||||
**After (Optimized)**:
|
||||
```tsx
|
||||
import { ThemeProvider, ThemeToggle } from './theme-provider';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<ThemeProvider defaultTheme="system">
|
||||
<YourApp />
|
||||
<ThemeToggle /> {/* One-line dark mode toggle */}
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
|
||||
// Automatically includes:
|
||||
// - Light/Dark/System detection
|
||||
// - localStorage persistence
|
||||
// - Smooth transitions
|
||||
// - Icon states
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚡ Performance Optimizations
|
||||
|
||||
### 1. Build-time Tailwind (Not CDN)
|
||||
|
||||
**Before**: CDN approach allowed
|
||||
**After**: Build-time mandatory
|
||||
|
||||
```javascript
|
||||
// Before: 400KB+ loaded every time
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
|
||||
// After: 2-15KB after tree-shaking
|
||||
npm install -D tailwindcss
|
||||
npx tailwindcss init
|
||||
```
|
||||
|
||||
**Impact**: 95% smaller CSS bundle
|
||||
|
||||
### 2. CSS Custom Properties
|
||||
|
||||
**Before**: Repeated color values
|
||||
**After**: Single source of truth
|
||||
|
||||
```css
|
||||
/* One definition, infinite reuse */
|
||||
:root {
|
||||
--primary: oklch(55% 0.18 250);
|
||||
}
|
||||
|
||||
.button { background: var(--primary); }
|
||||
.badge { color: var(--primary); }
|
||||
/* ... 100+ uses */
|
||||
```
|
||||
|
||||
**Impact**: Smaller bundle, easier theming
|
||||
|
||||
### 3. Component Composition
|
||||
|
||||
**Before**: Monolithic components
|
||||
**After**: Composable primitives
|
||||
|
||||
```tsx
|
||||
<Card>
|
||||
<Card.Header>
|
||||
<Card.Title>...</Card.Title>
|
||||
</Card.Header>
|
||||
<Card.Body>...</Card.Body>
|
||||
<Card.Footer>...</Card.Footer>
|
||||
</Card>
|
||||
```
|
||||
|
||||
**Impact**: Better tree-shaking, smaller bundles
|
||||
|
||||
---
|
||||
|
||||
## ✅ What Was Added (Not in Original)
|
||||
|
||||
1. ✨ **Complete TypeScript support** - All examples are type-safe
|
||||
2. 🎨 **Theme management system** - Production-ready provider
|
||||
3. 🧰 **Utility functions** - 30+ common helpers
|
||||
4. 📦 **Package.json** - Dependency reference
|
||||
5. 🎯 **Trigger patterns** - Clear skill activation
|
||||
6. 🔧 **Template files** - Copy-paste ready configs
|
||||
7. 📚 **Usage examples** - Real-world patterns
|
||||
8. 🎭 **Component library** - 10+ production components
|
||||
9. 🌗 **Dark mode system** - Complete implementation
|
||||
10. ♿ **Accessibility tests** - Specific test cases
|
||||
11. 🎬 **Animation system** - Keyframes + reduced motion
|
||||
12. 📱 **Mobile-first examples** - Responsive patterns
|
||||
13. 🔍 **SEO considerations** - Semantic HTML guide
|
||||
14. 🎨 **Design direction templates** - 5 complete styles
|
||||
15. 📖 **README** - Quick start guide
|
||||
|
||||
---
|
||||
|
||||
## 🎓 Learning Path
|
||||
|
||||
For developers using this skill:
|
||||
|
||||
1. **Day 1**: Read SKILL.md overview, understand token system
|
||||
2. **Day 2**: Explore CSS examples, try modifying tokens
|
||||
3. **Day 3**: Build first component using TypeScript examples
|
||||
4. **Day 4**: Create a page with multiple components
|
||||
5. **Day 5**: Implement theme toggle, test dark mode
|
||||
6. **Week 2**: Build complete project using the system
|
||||
|
||||
---
|
||||
|
||||
## 🔮 Future Enhancements (Not in v2.0)
|
||||
|
||||
Potential additions for v3.0:
|
||||
- Animation library (Framer Motion integration)
|
||||
- Form validation patterns
|
||||
- Data visualization components
|
||||
- Mobile gesture handlers
|
||||
- Internationalization (i18n) support
|
||||
- Server component examples (Next.js 13+)
|
||||
- Testing examples (Jest, Testing Library)
|
||||
- Storybook integration guide
|
||||
|
||||
---
|
||||
|
||||
## 📊 Metrics
|
||||
|
||||
- **Documentation**: 18,000+ words
|
||||
- **Code Examples**: 4,000+ lines
|
||||
- **Components**: 15 production-ready
|
||||
- **Utilities**: 30+ helper functions
|
||||
- **Design Tokens**: 100+ defined
|
||||
- **States Covered**: 8 per component
|
||||
- **Accessibility**: WCAG AA compliant
|
||||
- **Browser Support**: Modern browsers (last 2 versions)
|
||||
- **Bundle Size**: ~2-15KB (production, gzipped)
|
||||
|
||||
---
|
||||
|
||||
## 💡 Key Takeaways
|
||||
|
||||
This optimized version transforms a good methodology into a **complete, production-ready design system** with:
|
||||
|
||||
✅ **Better Developer Experience**: Copy-paste ready code
|
||||
✅ **Higher Quality Output**: Systematic + creative
|
||||
✅ **Faster Development**: Pre-built components
|
||||
✅ **Easier Maintenance**: Token-based system
|
||||
✅ **Better Accessibility**: Built-in WCAG compliance
|
||||
✅ **Modern Stack**: TypeScript, React, Tailwind
|
||||
✅ **Complete Documentation**: 20,000+ words total
|
||||
✅ **Real Examples**: Production patterns
|
||||
|
||||
The original document provided methodology; this version provides **implementation**.
|
||||
287
skills/frontend-design/README.md
Executable file
287
skills/frontend-design/README.md
Executable file
@@ -0,0 +1,287 @@
|
||||
# Frontend Design Skill
|
||||
|
||||
A comprehensive skill for transforming UI style requirements into production-ready frontend code with systematic design tokens, accessibility compliance, and creative execution.
|
||||
|
||||
## 📦 Skill Location
|
||||
|
||||
```
|
||||
{project_path}/skills/frontend-design/
|
||||
```
|
||||
|
||||
## 📚 What's Included
|
||||
|
||||
### Documentation
|
||||
- **SKILL.md** - Complete methodology and guidelines for frontend development
|
||||
- **README.md** - This file (quick start and overview)
|
||||
- **LICENSE** - MIT License
|
||||
|
||||
### CSS Examples (`examples/css/`)
|
||||
- **tokens.css** - Complete design token system with semantic colors, typography, spacing, radius, shadows, and motion tokens
|
||||
- **components.css** - Production-ready component styles (buttons, inputs, cards, modals, alerts, etc.)
|
||||
- **utilities.css** - Utility classes for layout, typography, states, and responsive design
|
||||
|
||||
### TypeScript Examples (`examples/typescript/`)
|
||||
- **design-tokens.ts** - Type-safe token definitions and utilities
|
||||
- **theme-provider.tsx** - Complete theme management system (light/dark/system modes)
|
||||
- **sample-components.tsx** - Production React components with full TypeScript support
|
||||
- **utils.ts** - Utility functions for frontend development
|
||||
|
||||
### Templates (`templates/`)
|
||||
- **tailwind.config.js** - Optimized Tailwind CSS configuration
|
||||
- **globals.css** - Global styles and CSS custom properties
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### When to Use This Skill
|
||||
|
||||
Use this skill when:
|
||||
- Building websites, web applications, or web components
|
||||
- User mentions design styles: "modern", "premium", "minimalist", "dark mode"
|
||||
- Creating dashboards, landing pages, or any web UI
|
||||
- User asks to "make it look better" or "improve the design"
|
||||
- User specifies frameworks: React, Vue, Svelte, Next.js, etc.
|
||||
|
||||
### Basic Usage
|
||||
|
||||
1. **Read SKILL.md** first for complete methodology
|
||||
2. **Choose a design direction** (Minimal SaaS, Bold Editorial, Soft & Organic, Dark Neon, Playful)
|
||||
3. **Generate design tokens** using the token system
|
||||
4. **Build components** using the provided examples
|
||||
5. **Compose pages** from components
|
||||
6. **Review & validate** against the checklist
|
||||
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
npm install -D tailwindcss postcss autoprefixer
|
||||
npm install clsx tailwind-merge
|
||||
|
||||
# Initialize Tailwind
|
||||
npx tailwindcss init -p
|
||||
|
||||
# Copy templates
|
||||
cp templates/tailwind.config.js ./tailwind.config.js
|
||||
cp templates/globals.css ./src/globals.css
|
||||
|
||||
# Import in your app
|
||||
# React: import './globals.css' in main entry
|
||||
# Next.js: import './globals.css' in _app.tsx or layout.tsx
|
||||
```
|
||||
|
||||
## 🎨 Design Tokens System
|
||||
|
||||
All visual properties derive from semantic tokens:
|
||||
|
||||
### Colors
|
||||
```css
|
||||
--background, --surface, --text
|
||||
--primary, --secondary, --accent
|
||||
--success, --warning, --danger, --info
|
||||
```
|
||||
|
||||
### Typography
|
||||
```css
|
||||
--font-size-{xs, sm, base, lg, xl, 2xl, 3xl, 4xl, 5xl}
|
||||
--line-height-{tight, snug, normal, relaxed, loose}
|
||||
--font-weight-{light, normal, medium, semibold, bold}
|
||||
```
|
||||
|
||||
### Spacing (8px system)
|
||||
```css
|
||||
--spacing-{0.5, 1, 2, 3, 4, 6, 8, 10, 12, 16, 20, 24, 32, 40, 48}
|
||||
```
|
||||
|
||||
### Radius
|
||||
```css
|
||||
--radius-{xs, sm, md, lg, xl, 2xl, 3xl, full}
|
||||
```
|
||||
|
||||
## 📖 Example Usage
|
||||
|
||||
### React Component with Tokens
|
||||
|
||||
```tsx
|
||||
import { Button, Card, Input } from './examples/typescript/sample-components';
|
||||
import { ThemeProvider } from './examples/typescript/theme-provider';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<ThemeProvider defaultTheme="system">
|
||||
<Card>
|
||||
<Card.Header>
|
||||
<Card.Title>Sign Up</Card.Title>
|
||||
<Card.Description>Create your account</Card.Description>
|
||||
</Card.Header>
|
||||
<Card.Body>
|
||||
<Input
|
||||
label="Email"
|
||||
type="email"
|
||||
placeholder="you@example.com"
|
||||
required
|
||||
/>
|
||||
<Input
|
||||
label="Password"
|
||||
type="password"
|
||||
placeholder="••••••••"
|
||||
required
|
||||
/>
|
||||
</Card.Body>
|
||||
<Card.Footer>
|
||||
<Button variant="primary">Create Account</Button>
|
||||
</Card.Footer>
|
||||
</Card>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### CSS-Only Approach
|
||||
|
||||
```css
|
||||
@import './examples/css/tokens.css';
|
||||
@import './examples/css/components.css';
|
||||
```
|
||||
|
||||
```html
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Sign Up</h3>
|
||||
<p class="card-description">Create your account</p>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="form-group">
|
||||
<label class="label">Email</label>
|
||||
<input type="email" class="input" placeholder="you@example.com" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<button class="btn btn-primary">Create Account</button>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
## ✨ Features
|
||||
|
||||
### ✅ Systematic Design
|
||||
- Token-first methodology
|
||||
- Consistent spacing (8px system)
|
||||
- Predictable visual hierarchy
|
||||
- Maintainable codebase
|
||||
|
||||
### ✅ Accessibility
|
||||
- WCAG AA compliance (minimum)
|
||||
- Keyboard navigation
|
||||
- Screen reader support
|
||||
- Focus management
|
||||
- Proper ARIA labels
|
||||
|
||||
### ✅ Responsive Design
|
||||
- Mobile-first approach
|
||||
- Fluid typography
|
||||
- Flexible layouts
|
||||
- Touch-friendly (44px+ targets)
|
||||
|
||||
### ✅ Dark Mode
|
||||
- Built-in theme system
|
||||
- CSS custom properties
|
||||
- System preference detection
|
||||
- Persistent user choice
|
||||
|
||||
### ✅ Production Ready
|
||||
- TypeScript support
|
||||
- Full type safety
|
||||
- Optimized bundle size
|
||||
- Tree-shaking enabled
|
||||
|
||||
## 📋 Component States
|
||||
|
||||
All components include:
|
||||
- **Default** - Base appearance
|
||||
- **Hover** - Visual feedback
|
||||
- **Active** - Pressed state
|
||||
- **Focus** - Keyboard indicator
|
||||
- **Disabled** - Inactive state
|
||||
- **Loading** - Skeleton/spinner
|
||||
- **Empty** - No data state
|
||||
- **Error** - Error recovery
|
||||
|
||||
## 🎯 Best Practices
|
||||
|
||||
1. **Always start with tokens** - Never skip to components
|
||||
2. **Use semantic colors** - No hardcoded hex values
|
||||
3. **Mobile-first** - Design for 375px, enhance upward
|
||||
4. **Accessibility first** - Build it in, not on
|
||||
5. **Test all states** - Default, hover, focus, disabled, loading, error
|
||||
6. **DRY principles** - Reusable components over duplicated code
|
||||
|
||||
## 🔧 Customization
|
||||
|
||||
### Extend Design Tokens
|
||||
|
||||
```typescript
|
||||
import { lightThemeTokens, mergeTokens } from './examples/typescript/design-tokens';
|
||||
|
||||
const customTokens = mergeTokens(lightThemeTokens, {
|
||||
colors: {
|
||||
primary: 'oklch(60% 0.20 280)', // Custom purple
|
||||
// ... other overrides
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Add Custom Components
|
||||
|
||||
Follow the patterns in `examples/typescript/sample-components.tsx`:
|
||||
1. Define TypeScript interfaces
|
||||
2. Implement with token-based styling
|
||||
3. Include all states
|
||||
4. Add accessibility features
|
||||
5. Document usage
|
||||
|
||||
## 📚 Documentation Structure
|
||||
|
||||
```
|
||||
frontend-design/
|
||||
├── SKILL.md # Complete methodology (READ THIS FIRST)
|
||||
├── README.md # Quick start guide (this file)
|
||||
├── LICENSE # MIT License
|
||||
├── examples/
|
||||
│ ├── css/
|
||||
│ │ ├── tokens.css # Design token system
|
||||
│ │ ├── components.css # Component styles
|
||||
│ │ └── utilities.css # Utility classes
|
||||
│ └── typescript/
|
||||
│ ├── design-tokens.ts # Type-safe tokens
|
||||
│ ├── theme-provider.tsx # Theme management
|
||||
│ ├── sample-components.tsx # React components
|
||||
│ └── utils.ts # Utility functions
|
||||
└── templates/
|
||||
├── tailwind.config.js # Tailwind configuration
|
||||
└── globals.css # Global styles
|
||||
```
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
This skill is maintained as part of the z-ai platform. To suggest improvements:
|
||||
1. Review the existing patterns
|
||||
2. Propose changes that enhance consistency
|
||||
3. Ensure all examples remain production-ready
|
||||
4. Update documentation accordingly
|
||||
|
||||
## 📄 License
|
||||
|
||||
MIT License - see LICENSE file for details
|
||||
|
||||
## 🔗 Resources
|
||||
|
||||
- [Tailwind CSS Documentation](https://tailwindcss.com/docs)
|
||||
- [WCAG Guidelines](https://www.w3.org/WAI/WCAG21/quickref/)
|
||||
- [shadcn/ui](https://ui.shadcn.com)
|
||||
- [TypeScript](https://www.typescriptlang.org)
|
||||
|
||||
---
|
||||
|
||||
**Version**: 2.0.0
|
||||
**Last Updated**: December 2024
|
||||
**Maintained by**: z-ai platform team
|
||||
981
skills/frontend-design/SKILL.md
Executable file
981
skills/frontend-design/SKILL.md
Executable file
@@ -0,0 +1,981 @@
|
||||
---
|
||||
name: frontend-design
|
||||
description: Transform UI style requirements into production-ready frontend code with systematic design tokens, accessibility compliance, and creative execution. Use when building websites, web applications, React/Vue components, dashboards, landing pages, or any web UI requiring both design consistency and aesthetic quality.
|
||||
version: 2.0.0
|
||||
license: MIT
|
||||
---
|
||||
|
||||
# Frontend Design Skill — Systematic & Creative Web Development
|
||||
|
||||
**Skill Location**: `{project_path}/skills/frontend-design/`
|
||||
|
||||
This skill transforms vague UI style requirements into executable, production-grade frontend code through a systematic design token approach while maintaining creative excellence. It ensures visual consistency, accessibility compliance, and maintainability across all deliverables.
|
||||
|
||||
---
|
||||
|
||||
## When to Use This Skill (Trigger Patterns)
|
||||
|
||||
**MUST apply this skill when:**
|
||||
|
||||
- User requests any website, web application, or web component development
|
||||
- User mentions design styles: "modern", "premium", "minimalist", "dark mode", "SaaS-style"
|
||||
- Building dashboards, landing pages, admin panels, or any web UI
|
||||
- User asks to "make it look better" or "improve the design"
|
||||
- Creating component libraries or design systems
|
||||
- User specifies frameworks: React, Vue, Svelte, Next.js, Nuxt, etc.
|
||||
- Converting designs/mockups to code
|
||||
- User mentions: Tailwind CSS, shadcn/ui, Material-UI, Chakra UI, etc.
|
||||
|
||||
**Trigger phrases:**
|
||||
- "build a website/app/component"
|
||||
- "create a dashboard/landing page"
|
||||
- "design a UI for..."
|
||||
- "make it modern/clean/premium"
|
||||
- "style this with..."
|
||||
- "convert this design to code"
|
||||
|
||||
**DO NOT use for:**
|
||||
- Backend API development
|
||||
- Pure logic/algorithm implementation
|
||||
- Non-visual code tasks
|
||||
|
||||
---
|
||||
|
||||
## Skill Architecture
|
||||
|
||||
This skill provides:
|
||||
|
||||
1. **SKILL.md** (this file): Core methodology and guidelines
|
||||
2. **examples/css/**: Production-ready CSS examples
|
||||
- `tokens.css` - Design token system
|
||||
- `components.css` - Reusable component styles
|
||||
- `utilities.css` - Utility classes
|
||||
3. **examples/typescript/**: TypeScript implementation examples
|
||||
- `design-tokens.ts` - Type-safe token definitions
|
||||
- `theme-provider.tsx` - Theme management
|
||||
- `sample-components.tsx` - Component examples
|
||||
4. **templates/**: Quick-start templates
|
||||
- `tailwind-config.js` - Tailwind configuration
|
||||
- `globals.css` - Global styles template
|
||||
|
||||
---
|
||||
|
||||
## Core Principles (Non-Negotiable)
|
||||
|
||||
### 1. **Dual-Mode Thinking: System + Creativity**
|
||||
|
||||
**Systematic Foundation:**
|
||||
- Design tokens first, UI components second
|
||||
- No arbitrary hardcoded values (colors, spacing, shadows, radius)
|
||||
- Consistent scales for typography, spacing, radius, elevation
|
||||
- Complete state coverage (default/hover/active/focus/disabled + loading/empty/error)
|
||||
- Accessibility as a constraint, not an afterthought
|
||||
|
||||
**Creative Execution:**
|
||||
- AVOID generic "AI slop" aesthetics (Inter/Roboto fonts, purple gradients, cookie-cutter layouts)
|
||||
- Choose BOLD aesthetic direction: brutalist, retro-futuristic, luxury, playful, editorial, etc.
|
||||
- Make unexpected choices in typography, color, layout, and motion
|
||||
- Each design should feel unique and intentionally crafted for its context
|
||||
|
||||
### 2. **Tokens-First Methodology**
|
||||
|
||||
```
|
||||
Design Tokens → Component Styles → Page Layouts → Interactive States
|
||||
```
|
||||
|
||||
**Never skip token definition.** All visual properties must derive from the token system.
|
||||
|
||||
### 3. **Tech Stack Flexibility**
|
||||
|
||||
**Default stack (if unspecified):**
|
||||
- Framework: React + TypeScript
|
||||
- Styling: Tailwind CSS
|
||||
- Components: shadcn/ui
|
||||
- Theme: CSS custom properties (light/dark modes)
|
||||
|
||||
**Supported alternatives:**
|
||||
- Frameworks: Vue, Svelte, Angular, vanilla HTML/CSS
|
||||
- Styling: CSS Modules, SCSS, Styled Components, Emotion
|
||||
- Libraries: MUI, Ant Design, Chakra UI, Headless UI
|
||||
|
||||
### 4. **Tailwind CSS Best Practices**
|
||||
|
||||
**⚠️ CRITICAL: Never use Tailwind via CDN**
|
||||
|
||||
**MUST use build-time integration:**
|
||||
```bash
|
||||
npm install -D tailwindcss postcss autoprefixer
|
||||
npx tailwindcss init -p
|
||||
```
|
||||
|
||||
**Why build-time is mandatory:**
|
||||
- ✅ Enables tree-shaking (2-15KB vs 400KB+ bundle)
|
||||
- ✅ Full design token customization
|
||||
- ✅ IDE autocomplete and type safety
|
||||
- ✅ Integrates with bundlers (Vite, webpack, Next.js)
|
||||
|
||||
**CDN only acceptable for:**
|
||||
- Quick prototypes/demos
|
||||
- Internal testing
|
||||
|
||||
---
|
||||
|
||||
## Implementation Workflow
|
||||
|
||||
### Phase 1: Design Analysis & Token Definition
|
||||
|
||||
**Step 1: Understand Context**
|
||||
```
|
||||
- Purpose: What problem does this solve? Who uses it?
|
||||
- Aesthetic Direction: Choose ONE bold direction
|
||||
- Technical Constraints: Framework, performance, accessibility needs
|
||||
- Differentiation: What makes this memorable?
|
||||
```
|
||||
|
||||
**Step 2: Generate Design Tokens**
|
||||
|
||||
Create comprehensive token system (see `examples/css/tokens.css` and `examples/typescript/design-tokens.ts`):
|
||||
|
||||
1. **Semantic Color Slots** (light + dark modes):
|
||||
```
|
||||
--background, --surface, --surface-subtle
|
||||
--text, --text-secondary, --text-muted
|
||||
--border, --border-subtle
|
||||
--primary, --primary-hover, --primary-active, --primary-foreground
|
||||
--secondary, --secondary-hover, --secondary-foreground
|
||||
--accent, --success, --warning, --danger
|
||||
```
|
||||
|
||||
2. **Typography Scale**:
|
||||
```
|
||||
Display: 3.5rem/4rem (56px/64px), weight 700-800
|
||||
H1: 2.5rem/3rem (40px/48px), weight 700
|
||||
H2: 2rem/2.5rem (32px/40px), weight 600
|
||||
H3: 1.5rem/2rem (24px/32px), weight 600
|
||||
Body: 1rem/1.5rem (16px/24px), weight 400
|
||||
Small: 0.875rem/1.25rem (14px/20px), weight 400
|
||||
Caption: 0.75rem/1rem (12px/16px), weight 400
|
||||
```
|
||||
|
||||
3. **Spacing Scale** (8px system):
|
||||
```
|
||||
0.5 → 4px, 1 → 8px, 2 → 16px, 3 → 24px, 4 → 32px
|
||||
5 → 40px, 6 → 48px, 8 → 64px, 12 → 96px, 16 → 128px
|
||||
```
|
||||
|
||||
4. **Radius Scale**:
|
||||
```
|
||||
xs: 2px (badges, tags)
|
||||
sm: 4px (buttons, inputs)
|
||||
md: 6px (cards, modals)
|
||||
lg: 8px (large cards, panels)
|
||||
xl: 12px (hero sections)
|
||||
2xl: 16px (special elements)
|
||||
full: 9999px (pills, avatars)
|
||||
```
|
||||
|
||||
5. **Shadow Scale**:
|
||||
```
|
||||
sm: Subtle lift (buttons, inputs)
|
||||
md: Card elevation
|
||||
lg: Modals, dropdowns
|
||||
xl: Large modals, drawers
|
||||
```
|
||||
|
||||
6. **Motion Tokens**:
|
||||
```
|
||||
Duration: 150ms (micro), 220ms (default), 300ms (complex)
|
||||
Easing: ease-out (enter), ease-in (exit), ease-in-out (transition)
|
||||
```
|
||||
|
||||
### Phase 2: Component Development
|
||||
|
||||
**Step 3: Build Reusable Components**
|
||||
|
||||
Follow this structure (see `examples/typescript/sample-components.tsx`):
|
||||
|
||||
```typescript
|
||||
interface ComponentProps {
|
||||
variant?: 'primary' | 'secondary' | 'outline' | 'ghost';
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
state?: 'default' | 'hover' | 'active' | 'disabled' | 'loading';
|
||||
}
|
||||
```
|
||||
|
||||
**Required component states:**
|
||||
- Default, Hover, Active, Focus, Disabled
|
||||
- Loading (skeleton/spinner)
|
||||
- Empty state (clear messaging)
|
||||
- Error state (recovery instructions)
|
||||
|
||||
**Required component features:**
|
||||
- Accessible (ARIA labels, keyboard navigation)
|
||||
- Responsive (mobile-first)
|
||||
- Theme-aware (light/dark mode)
|
||||
- Token-based styling (no hardcoded values)
|
||||
|
||||
### Phase 3: Page Assembly
|
||||
|
||||
**Step 4: Compose Pages from Components**
|
||||
|
||||
```
|
||||
- Use established tokens and components only
|
||||
- Mobile-first responsive design
|
||||
- Loading states for async content
|
||||
- Empty states with clear CTAs
|
||||
- Error states with recovery options
|
||||
```
|
||||
|
||||
### Phase 4: Quality Assurance
|
||||
|
||||
**Step 5: Self-Review Checklist**
|
||||
|
||||
- [ ] All colors from semantic tokens (no random hex/rgb)
|
||||
- [ ] All spacing from spacing scale
|
||||
- [ ] All radius from radius scale
|
||||
- [ ] Shadows justified by hierarchy
|
||||
- [ ] Clear type hierarchy with comfortable line-height (1.5+)
|
||||
- [ ] All interactive states implemented and tested
|
||||
- [ ] Accessibility: WCAG AA contrast, keyboard navigation, ARIA, focus indicators
|
||||
- [ ] Responsive: works on mobile (375px), tablet (768px), desktop (1024px+)
|
||||
- [ ] Loading/empty/error states included
|
||||
- [ ] Code is maintainable: DRY, clear naming, documented
|
||||
|
||||
---
|
||||
|
||||
## Design Direction Templates
|
||||
|
||||
### 1. Minimal Premium SaaS (Most Universal)
|
||||
|
||||
```
|
||||
Visual Style: Minimal Premium SaaS
|
||||
- Generous whitespace (1.5-2x standard padding)
|
||||
- Near-white background with subtle surface contrast
|
||||
- Light borders (1px, low-opacity)
|
||||
- Very subtle elevation (avoid heavy shadows)
|
||||
- Unified control height: 44-48px
|
||||
- Medium-large radius: 6-8px
|
||||
- Gentle hover states (background shift only)
|
||||
- Clear but not harsh focus rings
|
||||
- Low-contrast dividers
|
||||
- Priority: Readability and consistency
|
||||
```
|
||||
|
||||
**Best for:** Enterprise apps, B2B SaaS, productivity tools
|
||||
|
||||
### 2. Bold Editorial
|
||||
|
||||
```
|
||||
Visual Style: Bold Editorial
|
||||
- Strong typographic hierarchy (large display fonts)
|
||||
- High contrast (black/white or dark/light extremes)
|
||||
- Generous use of negative space
|
||||
- Asymmetric layouts with intentional imbalance
|
||||
- Grid-breaking elements
|
||||
- Minimal color palette (1-2 accent colors max)
|
||||
- Sharp, geometric shapes
|
||||
- Dramatic scale differences
|
||||
- Priority: Visual impact and memorability
|
||||
```
|
||||
|
||||
**Best for:** Marketing sites, portfolios, content-heavy sites
|
||||
|
||||
### 3. Soft & Organic
|
||||
|
||||
```
|
||||
Visual Style: Soft & Organic
|
||||
- Rounded corners everywhere (12-24px radius)
|
||||
- Soft shadows and subtle gradients
|
||||
- Pastel or muted color palette
|
||||
- Gentle animations (ease-in-out, 300-400ms)
|
||||
- Curved elements and flowing layouts
|
||||
- Generous padding (1.5-2x standard)
|
||||
- Soft, blurred backgrounds
|
||||
- Priority: Approachability and comfort
|
||||
```
|
||||
|
||||
**Best for:** Consumer apps, wellness, lifestyle brands
|
||||
|
||||
### 4. Dark Neon (Restrained)
|
||||
|
||||
```
|
||||
Visual Style: Dark Neon
|
||||
- Dark background (#0a0a0a to #1a1a1a, NOT pure black)
|
||||
- High contrast text (#ffffff or #fafafa)
|
||||
- Accent colors ONLY for CTAs and key states
|
||||
- Subtle glow on hover (box-shadow with accent color)
|
||||
- Minimal borders (use subtle outlines)
|
||||
- Optional: Subtle noise texture
|
||||
- Restrained use of neon (less is more)
|
||||
- Priority: Focus and sophisticated edge
|
||||
```
|
||||
|
||||
**Best for:** Developer tools, gaming, tech products
|
||||
|
||||
### 5. Playful & Colorful
|
||||
|
||||
```
|
||||
Visual Style: Playful & Colorful
|
||||
- Vibrant color palette (3-5 colors)
|
||||
- Rounded corners (8-16px)
|
||||
- Micro-animations on hover/interaction
|
||||
- Generous padding and breathing room
|
||||
- Friendly, geometric illustrations
|
||||
- Smooth transitions (200-250ms)
|
||||
- High energy but balanced
|
||||
- Priority: Delight and engagement
|
||||
```
|
||||
|
||||
**Best for:** Consumer apps, children's products, creative tools
|
||||
|
||||
---
|
||||
|
||||
## Standard Prompting Workflow
|
||||
|
||||
### Master Prompt Template
|
||||
|
||||
```
|
||||
You are a Design Systems Engineer + Senior Frontend UI Developer with expertise in creative design execution.
|
||||
|
||||
[TECH STACK]
|
||||
- Framework: {{FRAMEWORK = React + TypeScript}}
|
||||
- Styling: {{STYLING = Tailwind CSS}}
|
||||
- Components: {{UI_LIB = shadcn/ui}}
|
||||
- Theme: CSS variables (light/dark modes)
|
||||
|
||||
[DESIGN SYSTEM RULES - MANDATORY]
|
||||
1. Layout: 8px spacing system; mobile-first responsive
|
||||
2. Typography: Clear hierarchy (Display/H1/H2/H3/Body/Small/Caption); line-height 1.5+
|
||||
3. Colors: Semantic tokens ONLY (no hardcoded values)
|
||||
4. Shape: Tiered radius system; tap targets ≥ 44px
|
||||
5. Elevation: Minimal shadows; borders for hierarchy
|
||||
6. Motion: Subtle transitions (150-220ms); restrained animations
|
||||
7. Accessibility: WCAG AA; keyboard navigation; ARIA; focus indicators
|
||||
|
||||
[AESTHETIC DIRECTION]
|
||||
Style: {{STYLE = Minimal Premium SaaS}}
|
||||
Key Differentiator: {{UNIQUE_FEATURE}}
|
||||
Target Audience: {{AUDIENCE}}
|
||||
|
||||
[INTERACTION STATES - REQUIRED]
|
||||
✓ Default, Hover, Active, Focus, Disabled
|
||||
✓ Loading (skeleton), Empty (with messaging), Error (with recovery)
|
||||
|
||||
[OUTPUT REQUIREMENTS]
|
||||
1. Design Tokens (CSS variables + TypeScript types)
|
||||
2. Component implementations (copy-paste ready)
|
||||
3. Page layouts with all states
|
||||
4. NO hardcoded values; reference tokens only
|
||||
5. Minimal but clear code comments
|
||||
```
|
||||
|
||||
### Token Generation Prompt
|
||||
|
||||
```
|
||||
Generate a complete Design Token system including:
|
||||
|
||||
1. Semantic color slots (CSS custom properties):
|
||||
- Light mode + Dark mode variants
|
||||
- Background, surface, text, border, primary, secondary, accent, semantic colors
|
||||
- Interactive states for each (hover, active)
|
||||
|
||||
2. Typography scale:
|
||||
- Display, H1-H6, Body, Small, Caption, Monospace
|
||||
- Include: font-size, line-height, font-weight, letter-spacing
|
||||
|
||||
3. Spacing scale (8px base):
|
||||
- 0.5, 1, 2, 3, 4, 5, 6, 8, 10, 12, 16, 20, 24 (in rem)
|
||||
|
||||
4. Radius scale:
|
||||
- xs (2px), sm (4px), md (6px), lg (8px), xl (12px), 2xl (16px), full
|
||||
|
||||
5. Shadow scale:
|
||||
- sm, md, lg, xl (with color and blur values)
|
||||
- Usage guidelines for each tier
|
||||
|
||||
6. Motion tokens:
|
||||
- Duration: fast (150ms), base (220ms), slow (300ms)
|
||||
- Easing: ease-out, ease-in, ease-in-out
|
||||
|
||||
7. Component density:
|
||||
- Button heights: sm (36px), md (44px), lg (48px)
|
||||
- Input heights: sm (36px), md (40px)
|
||||
- Padding scales
|
||||
|
||||
Output format:
|
||||
- CSS custom properties (globals.css)
|
||||
- Tailwind config integration
|
||||
- TypeScript type definitions
|
||||
- Usage examples for each token category
|
||||
|
||||
DO NOT write component code yet.
|
||||
```
|
||||
|
||||
### Component Implementation Prompt
|
||||
|
||||
```
|
||||
Using the established Design Tokens, implement: <{{COMPONENT_NAME}} />
|
||||
|
||||
Requirements:
|
||||
- Props: variant, size, state, className (for composition)
|
||||
- States: default, hover, focus, active, disabled, loading, error
|
||||
- Accessibility: keyboard navigation, ARIA labels, focus management
|
||||
- Responsive: mobile-first, touch-friendly (44px+ tap targets)
|
||||
- Styling: Use tokens ONLY (no hardcoded values)
|
||||
- TypeScript: Full type safety with exported interfaces
|
||||
|
||||
Include:
|
||||
1. Component implementation
|
||||
2. Usage examples (3-5 variants)
|
||||
3. Loading state example
|
||||
4. Error state example
|
||||
5. Accessibility notes
|
||||
|
||||
Output: Production-ready, copy-paste code with JSDoc comments.
|
||||
```
|
||||
|
||||
### Page Development Prompt
|
||||
|
||||
```
|
||||
Build page: {{PAGE_NAME}}
|
||||
|
||||
Using:
|
||||
- Established Design Tokens
|
||||
- Implemented Components
|
||||
- {{STYLE}} aesthetic direction
|
||||
|
||||
Requirements:
|
||||
- Responsive layout (mobile/tablet/desktop)
|
||||
- All interaction states (hover/focus/active/disabled)
|
||||
- Loading skeleton for async content
|
||||
- Empty state with clear CTA
|
||||
- Error state with recovery options
|
||||
- Accessible (keyboard nav, ARIA, WCAG AA)
|
||||
- No hardcoded styles (components + utility classes only)
|
||||
|
||||
Include:
|
||||
1. Page component with mock data
|
||||
2. Loading state variant
|
||||
3. Empty state variant
|
||||
4. Error state variant
|
||||
5. Responsive behavior notes
|
||||
|
||||
Output: Complete, runnable page component.
|
||||
```
|
||||
|
||||
### Review & Optimization Prompt
|
||||
|
||||
```
|
||||
You are a Frontend Code Reviewer specializing in design systems and accessibility.
|
||||
|
||||
Review the implementation and check:
|
||||
|
||||
1. Token Compliance:
|
||||
- Any hardcoded colors, sizes, shadows, radius?
|
||||
- All values from established scales?
|
||||
|
||||
2. Typography:
|
||||
- Clear hierarchy?
|
||||
- Comfortable line-height (1.5+)?
|
||||
- Appropriate font sizes for each level?
|
||||
|
||||
3. Spacing & Layout:
|
||||
- Consistent use of spacing scale?
|
||||
- Adequate whitespace?
|
||||
- No awkward gaps or cramped sections?
|
||||
|
||||
4. Interactive States:
|
||||
- Hover/focus/active clearly distinct?
|
||||
- Disabled state obviously different?
|
||||
- Loading/empty/error states implemented?
|
||||
|
||||
5. Accessibility:
|
||||
- WCAG AA contrast met?
|
||||
- Keyboard reachable?
|
||||
- ARIA labels complete?
|
||||
- Focus indicators visible?
|
||||
- Semantic HTML?
|
||||
|
||||
6. Responsive Design:
|
||||
- Mobile layout functional (375px)?
|
||||
- Tablet optimized (768px)?
|
||||
- Desktop enhanced (1024px+)?
|
||||
- Touch targets ≥ 44px?
|
||||
|
||||
7. Maintainability:
|
||||
- DRY principles followed?
|
||||
- Clear component boundaries?
|
||||
- Consistent naming?
|
||||
- Adequate comments?
|
||||
|
||||
8. Creative Execution:
|
||||
- Matches intended aesthetic?
|
||||
- Avoids generic patterns?
|
||||
- Unique and memorable?
|
||||
|
||||
Output:
|
||||
- Findings (sorted by severity: Critical, High, Medium, Low)
|
||||
- Specific fixes (code patches)
|
||||
- Improvement suggestions
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Common Pitfalls & Solutions
|
||||
|
||||
### ❌ Problem: Vague aesthetic descriptions
|
||||
### ✅ Solution: Force actionable specifications
|
||||
|
||||
```
|
||||
DON'T: "Make it modern and clean"
|
||||
DO:
|
||||
- Whitespace: 1.5x standard padding (24px instead of 16px)
|
||||
- Typography: Display 56px, H1 40px, Body 16px, line-height 1.6
|
||||
- Colors: Neutral gray scale (50-900) + single accent color
|
||||
- Shadows: Maximum 2 shadow tokens (card + modal only)
|
||||
- Radius: Consistent 6px (buttons/inputs) and 8px (cards)
|
||||
- Borders: 1px with --border-subtle (#e5e7eb in light mode)
|
||||
- Transitions: 150ms ease-out only
|
||||
```
|
||||
|
||||
### ❌ Problem: Each component invents its own styles
|
||||
### ✅ Solution: Enforce token-only rule
|
||||
|
||||
```
|
||||
RULE: Every visual property must map to a token.
|
||||
|
||||
Violations:
|
||||
- ❌ bg-gray-100 (hardcoded Tailwind color)
|
||||
- ❌ p-[17px] (arbitrary padding not in scale)
|
||||
- ❌ rounded-[5px] (radius not in scale)
|
||||
- ❌ shadow-[0_2px_8px_rgba(0,0,0,0.1)] (arbitrary shadow)
|
||||
|
||||
Correct:
|
||||
- ✅ bg-surface (semantic token)
|
||||
- ✅ p-4 (maps to spacing scale: 16px)
|
||||
- ✅ rounded-md (maps to radius scale: 6px)
|
||||
- ✅ shadow-sm (maps to shadow token)
|
||||
```
|
||||
|
||||
### ❌ Problem: Missing interactive states
|
||||
### ✅ Solution: State coverage checklist
|
||||
|
||||
```
|
||||
For EVERY interactive element, implement:
|
||||
|
||||
Visual States:
|
||||
- [ ] Default (base appearance)
|
||||
- [ ] Hover (background shift, shadow, scale)
|
||||
- [ ] Active (pressed state, slightly darker)
|
||||
- [ ] Focus (visible ring, keyboard accessible)
|
||||
- [ ] Disabled (reduced opacity, cursor not-allowed)
|
||||
|
||||
Data States:
|
||||
- [ ] Loading (skeleton or spinner with same dimensions)
|
||||
- [ ] Empty (clear message + CTA)
|
||||
- [ ] Error (error message + retry option)
|
||||
|
||||
Test each state in isolation and in combination.
|
||||
```
|
||||
|
||||
### ❌ Problem: Generic AI aesthetics
|
||||
### ✅ Solution: Force creative differentiation
|
||||
|
||||
```
|
||||
BANNED PATTERNS (overused in AI-generated UIs):
|
||||
- ❌ Inter/Roboto/System fonts as primary choice
|
||||
- ❌ Purple gradients on white backgrounds
|
||||
- ❌ Card-grid-card-grid layouts only
|
||||
- ❌ Generic blue (#3b82f6) as primary
|
||||
- ❌ Default Tailwind color palette with no customization
|
||||
|
||||
REQUIRED CREATIVE CHOICES:
|
||||
- ✅ Select distinctive fonts (Google Fonts, Adobe Fonts, custom)
|
||||
- ✅ Build custom color palette (not Tailwind defaults)
|
||||
- ✅ Design unique layouts (asymmetry, overlap, grid-breaking)
|
||||
- ✅ Add personality: illustrations, icons, textures, patterns
|
||||
- ✅ Create signature elements (unique buttons, cards, headers)
|
||||
|
||||
Ask yourself: "Would someone recognize this as uniquely designed for this purpose?"
|
||||
```
|
||||
|
||||
### ❌ Problem: Accessibility as afterthought
|
||||
### ✅ Solution: Accessibility as constraint
|
||||
|
||||
```
|
||||
Build accessibility IN, not ON:
|
||||
|
||||
Color Contrast:
|
||||
- Run contrast checker on all text/background pairs
|
||||
- Minimum WCAG AA: 4.5:1 (normal text), 3:1 (large text)
|
||||
- Use tools: WebAIM Contrast Checker, Chrome DevTools
|
||||
|
||||
Keyboard Navigation:
|
||||
- Tab order follows visual flow
|
||||
- All interactive elements keyboard reachable
|
||||
- Focus indicator always visible (outline or ring)
|
||||
- Escape closes modals/dropdowns
|
||||
|
||||
ARIA & Semantics:
|
||||
- Use semantic HTML first (<button>, <nav>, <main>)
|
||||
- Add ARIA only when semantic HTML insufficient
|
||||
- aria-label for icon-only buttons
|
||||
- aria-describedby for form errors
|
||||
- aria-expanded for disclosure widgets
|
||||
|
||||
Test with:
|
||||
- Keyboard only (no mouse)
|
||||
- Screen reader (NVDA, JAWS, VoiceOver)
|
||||
- Reduced motion preference (prefers-reduced-motion)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Quick Start: Complete Example
|
||||
|
||||
```
|
||||
You are a Design Systems Engineer + Senior Frontend UI Developer.
|
||||
|
||||
[STACK]
|
||||
React + TypeScript + Tailwind CSS + shadcn/ui
|
||||
|
||||
[TASK]
|
||||
Build a Team Dashboard for a project management app.
|
||||
|
||||
[AESTHETIC]
|
||||
Style: Minimal Premium SaaS
|
||||
Unique Element: Subtle animated background gradient
|
||||
Audience: Product managers and software teams
|
||||
|
||||
[REQUIREMENTS]
|
||||
1. Components needed:
|
||||
- Header with search and user menu
|
||||
- Team members grid (name, role, avatar, status)
|
||||
- Invite modal (name, email, role selector)
|
||||
- Empty state (no team members yet)
|
||||
- Loading skeleton
|
||||
|
||||
2. Features:
|
||||
- Search/filter team members
|
||||
- Click to view member details
|
||||
- Invite button opens modal
|
||||
- Sort by name/role/status
|
||||
|
||||
3. States:
|
||||
- Loading (skeleton grid)
|
||||
- Empty (with invite CTA)
|
||||
- Populated (member cards)
|
||||
- Error (failed to load)
|
||||
|
||||
[OUTPUT]
|
||||
1. Design Tokens (globals.css + tailwind.config.ts)
|
||||
2. Component implementations:
|
||||
- TeamMemberCard
|
||||
- InviteModal
|
||||
- SearchBar
|
||||
- UserMenu
|
||||
3. TeamDashboard page component
|
||||
4. All states (loading/empty/error)
|
||||
5. Full TypeScript types
|
||||
6. Accessibility notes
|
||||
|
||||
Rules:
|
||||
- Mobile-first responsive
|
||||
- No hardcoded values (use tokens)
|
||||
- WCAG AA compliance
|
||||
- Include hover/focus/active states
|
||||
- Add subtle micro-interactions
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Examples & Templates
|
||||
|
||||
This skill includes production-ready examples in `examples/`:
|
||||
|
||||
### CSS Examples (`examples/css/`)
|
||||
|
||||
**`tokens.css`** — Complete design token system
|
||||
- Semantic color tokens (light + dark modes)
|
||||
- Typography scale with fluid sizing
|
||||
- Spacing, radius, shadow, motion scales
|
||||
- CSS custom properties ready to use
|
||||
|
||||
**`components.css`** — Component style library
|
||||
- Buttons (variants, sizes, states)
|
||||
- Inputs, textareas, selects
|
||||
- Cards, modals, tooltips
|
||||
- Navigation, headers, footers
|
||||
- Loading skeletons
|
||||
- All with state variants (hover/focus/active/disabled)
|
||||
|
||||
**`utilities.css`** — Utility class library
|
||||
- Layout utilities (flex, grid, container)
|
||||
- Spacing utilities (margin, padding)
|
||||
- Typography utilities (sizes, weights, line-heights)
|
||||
- State utilities (hover, focus, group variants)
|
||||
|
||||
### TypeScript Examples (`examples/typescript/`)
|
||||
|
||||
**`design-tokens.ts`** — Type-safe token definitions
|
||||
- Token interfaces and types
|
||||
- Design system configuration
|
||||
- Theme type definitions
|
||||
- Token validators
|
||||
|
||||
**`theme-provider.tsx`** — Theme management system
|
||||
- Theme context provider
|
||||
- Dark mode toggle
|
||||
- System preference detection
|
||||
- Theme persistence (localStorage)
|
||||
|
||||
**`sample-components.tsx`** — Production component examples
|
||||
- Button component (all variants)
|
||||
- Input component (with validation)
|
||||
- Card component (with loading states)
|
||||
- Modal component (with focus management)
|
||||
- All with full TypeScript types and accessibility
|
||||
|
||||
### Templates (`templates/`)
|
||||
|
||||
**`tailwind-config.js`** — Optimized Tailwind configuration
|
||||
- Custom color palette
|
||||
- Typography plugin setup
|
||||
- Spacing and sizing scales
|
||||
- Plugin configurations
|
||||
|
||||
**`globals.css`** — Global styles template
|
||||
- CSS reset/normalize
|
||||
- Token definitions
|
||||
- Base element styles
|
||||
- Utility classes
|
||||
|
||||
---
|
||||
|
||||
## Output Quality Standards
|
||||
|
||||
Every deliverable must meet:
|
||||
|
||||
### Code Quality
|
||||
- ✅ Production-ready (copy-paste deployable)
|
||||
- ✅ TypeScript with full type safety
|
||||
- ✅ ESLint/Prettier compliant
|
||||
- ✅ No hardcoded magic numbers
|
||||
- ✅ DRY (Don't Repeat Yourself)
|
||||
- ✅ Clear, descriptive naming
|
||||
- ✅ JSDoc comments for complex logic
|
||||
|
||||
### Design Quality
|
||||
- ✅ Unique, memorable aesthetic
|
||||
- ✅ Consistent token usage
|
||||
- ✅ Cohesive visual language
|
||||
- ✅ Thoughtful micro-interactions
|
||||
- ✅ Polished details (shadows, transitions, spacing)
|
||||
|
||||
### Accessibility Quality
|
||||
- ✅ WCAG AA minimum (AAA preferred)
|
||||
- ✅ Keyboard navigable
|
||||
- ✅ Screen reader friendly
|
||||
- ✅ Focus management
|
||||
- ✅ Semantic HTML
|
||||
- ✅ ARIA when necessary
|
||||
|
||||
### Performance Quality
|
||||
- ✅ Optimized bundle size (tree-shaking)
|
||||
- ✅ Lazy loading for heavy components
|
||||
- ✅ CSS-only animations when possible
|
||||
- ✅ Minimal re-renders (React memo/useMemo)
|
||||
- ✅ Responsive images (srcset, sizes)
|
||||
|
||||
---
|
||||
|
||||
## Verification Checklist
|
||||
|
||||
Before delivering code, verify:
|
||||
|
||||
**Tokens & System:**
|
||||
- [ ] All colors from semantic tokens (no hex/rgb hardcoded)
|
||||
- [ ] All spacing from spacing scale (8px system)
|
||||
- [ ] All radius from radius scale (xs/sm/md/lg/xl/2xl/full)
|
||||
- [ ] Shadows minimal and justified
|
||||
- [ ] Typography hierarchy clear (Display/H1/H2/H3/Body/Small/Caption)
|
||||
- [ ] Line-height comfortable (1.5+ for body text)
|
||||
|
||||
**States & Interactions:**
|
||||
- [ ] Default state implemented
|
||||
- [ ] Hover state (visual feedback)
|
||||
- [ ] Active state (pressed appearance)
|
||||
- [ ] Focus state (keyboard ring visible)
|
||||
- [ ] Disabled state (reduced opacity, no pointer)
|
||||
- [ ] Loading state (skeleton or spinner)
|
||||
- [ ] Empty state (clear message + CTA)
|
||||
- [ ] Error state (message + recovery)
|
||||
|
||||
**Accessibility:**
|
||||
- [ ] WCAG AA contrast (4.5:1 text, 3:1 large text)
|
||||
- [ ] Keyboard navigation complete
|
||||
- [ ] Focus indicators always visible
|
||||
- [ ] Semantic HTML used
|
||||
- [ ] ARIA labels where needed
|
||||
- [ ] Form labels associated
|
||||
- [ ] Alt text on images
|
||||
|
||||
**Responsive Design:**
|
||||
- [ ] Mobile layout (375px+) functional
|
||||
- [ ] Tablet layout (768px+) optimized
|
||||
- [ ] Desktop layout (1024px+) enhanced
|
||||
- [ ] Touch targets ≥ 44px
|
||||
- [ ] Text readable on all sizes
|
||||
- [ ] No horizontal scroll
|
||||
|
||||
**Creative Execution:**
|
||||
- [ ] Unique aesthetic (not generic)
|
||||
- [ ] Matches stated design direction
|
||||
- [ ] Memorable visual element
|
||||
- [ ] Cohesive design language
|
||||
- [ ] Polished details
|
||||
|
||||
**Code Quality:**
|
||||
- [ ] TypeScript types complete
|
||||
- [ ] No linter errors
|
||||
- [ ] DRY principles followed
|
||||
- [ ] Clear component boundaries
|
||||
- [ ] Consistent naming conventions
|
||||
- [ ] Adequate comments
|
||||
- [ ] Production-ready (can deploy as-is)
|
||||
|
||||
---
|
||||
|
||||
## Advanced Techniques
|
||||
|
||||
### 1. Fluid Typography
|
||||
|
||||
```css
|
||||
/* Responsive type scale using clamp() */
|
||||
:root {
|
||||
--font-size-sm: clamp(0.875rem, 0.8rem + 0.2vw, 1rem);
|
||||
--font-size-base: clamp(1rem, 0.9rem + 0.3vw, 1.125rem);
|
||||
--font-size-lg: clamp(1.125rem, 1rem + 0.4vw, 1.25rem);
|
||||
--font-size-xl: clamp(1.25rem, 1.1rem + 0.5vw, 1.5rem);
|
||||
--font-size-2xl: clamp(1.5rem, 1.3rem + 0.7vw, 2rem);
|
||||
--font-size-3xl: clamp(1.875rem, 1.5rem + 1vw, 2.5rem);
|
||||
--font-size-4xl: clamp(2.25rem, 1.8rem + 1.5vw, 3.5rem);
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Advanced Color Systems
|
||||
|
||||
```css
|
||||
/* Color with opacity variants using oklch */
|
||||
:root {
|
||||
--primary-base: oklch(60% 0.15 250);
|
||||
--primary-subtle: oklch(95% 0.02 250);
|
||||
--primary-muted: oklch(85% 0.05 250);
|
||||
--primary-emphasis: oklch(50% 0.18 250);
|
||||
--primary-foreground: oklch(98% 0.01 250);
|
||||
}
|
||||
|
||||
/* Dark mode: adjust lightness only */
|
||||
[data-theme="dark"] {
|
||||
--primary-base: oklch(70% 0.15 250);
|
||||
--primary-subtle: oklch(20% 0.02 250);
|
||||
--primary-muted: oklch(30% 0.05 250);
|
||||
--primary-emphasis: oklch(80% 0.18 250);
|
||||
--primary-foreground: oklch(10% 0.01 250);
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Skeleton Loading Patterns
|
||||
|
||||
```tsx
|
||||
// Animated skeleton with shimmer effect
|
||||
const Skeleton = ({ className }: { className?: string }) => (
|
||||
<div
|
||||
className={cn(
|
||||
"animate-pulse rounded-md bg-surface-subtle",
|
||||
"relative overflow-hidden",
|
||||
"before:absolute before:inset-0",
|
||||
"before:-translate-x-full before:animate-shimmer",
|
||||
"before:bg-gradient-to-r before:from-transparent before:via-white/10 before:to-transparent",
|
||||
className
|
||||
)}
|
||||
/>
|
||||
);
|
||||
|
||||
// Usage in components
|
||||
<Card>
|
||||
<Skeleton className="h-4 w-3/4 mb-2" />
|
||||
<Skeleton className="h-4 w-1/2 mb-4" />
|
||||
<Skeleton className="h-32 w-full" />
|
||||
</Card>
|
||||
```
|
||||
|
||||
### 4. Advanced Motion
|
||||
|
||||
```css
|
||||
/* Page transitions */
|
||||
@keyframes fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(8px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Staggered animations */
|
||||
.stagger-item {
|
||||
animation: fade-in 0.3s ease-out backwards;
|
||||
}
|
||||
.stagger-item:nth-child(1) { animation-delay: 0ms; }
|
||||
.stagger-item:nth-child(2) { animation-delay: 50ms; }
|
||||
.stagger-item:nth-child(3) { animation-delay: 100ms; }
|
||||
.stagger-item:nth-child(4) { animation-delay: 150ms; }
|
||||
|
||||
/* Respect reduced motion */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Tips for Excellence
|
||||
|
||||
1. **Always start with tokens** — Never skip to components
|
||||
2. **Think mobile-first** — Design for 375px, enhance upward
|
||||
3. **Validate states early** — Test each interactive state in isolation
|
||||
4. **Be bold with aesthetics** — Avoid generic patterns
|
||||
5. **Accessibility is non-negotiable** — Build it in from the start
|
||||
6. **Use real content** — Test with actual text, images, data
|
||||
7. **Review your own work** — Self-audit before delivering
|
||||
8. **Document decisions** — Explain complex styling choices
|
||||
9. **Keep it maintainable** — Future developers will thank you
|
||||
10. **Ship production-ready code** — No "TODO" or "FIXME" in deliverables
|
||||
|
||||
---
|
||||
|
||||
## References & Resources
|
||||
|
||||
- **Tailwind CSS**: https://tailwindcss.com/docs
|
||||
- **shadcn/ui**: https://ui.shadcn.com
|
||||
- **WCAG Guidelines**: https://www.w3.org/WAI/WCAG21/quickref/
|
||||
- **Color Contrast Checker**: https://webaim.org/resources/contrastchecker/
|
||||
- **Type Scale**: https://typescale.com
|
||||
- **Modular Scale**: https://www.modularscale.com
|
||||
- **CSS Custom Properties**: https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties
|
||||
|
||||
---
|
||||
|
||||
**Version**: 2.0.0
|
||||
**Last Updated**: December 2024
|
||||
**License**: MIT
|
||||
**Maintained by**: z-ai platform team
|
||||
842
skills/frontend-design/examples/css/components.css
Executable file
842
skills/frontend-design/examples/css/components.css
Executable file
@@ -0,0 +1,842 @@
|
||||
/**
|
||||
* Component Styles — Production-Ready UI Components
|
||||
*
|
||||
* This file provides complete component implementations using the design token system.
|
||||
* All components include full state coverage and accessibility features.
|
||||
*
|
||||
* Location: {project_path}/skills/frontend-design/examples/css/components.css
|
||||
*
|
||||
* Dependencies: tokens.css must be imported first
|
||||
*/
|
||||
|
||||
/* Import design tokens */
|
||||
@import './tokens.css';
|
||||
|
||||
|
||||
/* ============================================
|
||||
BUTTONS
|
||||
============================================ */
|
||||
|
||||
/* Base button styles */
|
||||
.btn {
|
||||
/* Layout */
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--spacing-2);
|
||||
|
||||
/* Sizing */
|
||||
height: var(--button-height-md);
|
||||
padding-inline: var(--spacing-6);
|
||||
|
||||
/* Typography */
|
||||
font-size: var(--font-size-base);
|
||||
font-weight: var(--font-weight-medium);
|
||||
line-height: 1;
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
|
||||
/* Appearance */
|
||||
border: 1px solid transparent;
|
||||
border-radius: var(--radius-sm);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
|
||||
/* Transitions */
|
||||
transition: var(--transition-colors), var(--transition-transform);
|
||||
|
||||
/* Accessibility */
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.btn:focus-visible {
|
||||
outline: var(--focus-ring-width) solid var(--focus-ring-color);
|
||||
outline-offset: var(--focus-ring-offset);
|
||||
}
|
||||
|
||||
/* Primary button */
|
||||
.btn-primary {
|
||||
background-color: var(--primary);
|
||||
color: var(--primary-foreground);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.btn-primary:hover:not(:disabled) {
|
||||
background-color: var(--primary-hover);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.btn-primary:active:not(:disabled) {
|
||||
background-color: var(--primary-active);
|
||||
transform: translateY(1px);
|
||||
box-shadow: var(--shadow-xs);
|
||||
}
|
||||
|
||||
/* Secondary button */
|
||||
.btn-secondary {
|
||||
background-color: var(--secondary);
|
||||
color: var(--secondary-foreground);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.btn-secondary:hover:not(:disabled) {
|
||||
background-color: var(--secondary-hover);
|
||||
}
|
||||
|
||||
.btn-secondary:active:not(:disabled) {
|
||||
background-color: var(--secondary-active);
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
/* Outline button */
|
||||
.btn-outline {
|
||||
background-color: transparent;
|
||||
color: var(--text);
|
||||
border-color: var(--border);
|
||||
}
|
||||
|
||||
.btn-outline:hover:not(:disabled) {
|
||||
background-color: var(--surface-hover);
|
||||
border-color: var(--border-strong);
|
||||
}
|
||||
|
||||
.btn-outline:active:not(:disabled) {
|
||||
background-color: var(--surface-subtle);
|
||||
}
|
||||
|
||||
/* Ghost button */
|
||||
.btn-ghost {
|
||||
background-color: transparent;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.btn-ghost:hover:not(:disabled) {
|
||||
background-color: var(--surface-hover);
|
||||
}
|
||||
|
||||
.btn-ghost:active:not(:disabled) {
|
||||
background-color: var(--surface-subtle);
|
||||
}
|
||||
|
||||
/* Danger button */
|
||||
.btn-danger {
|
||||
background-color: var(--danger);
|
||||
color: var(--danger-foreground);
|
||||
}
|
||||
|
||||
.btn-danger:hover:not(:disabled) {
|
||||
background-color: oklch(from var(--danger) calc(l - 0.05) c h);
|
||||
}
|
||||
|
||||
/* Button sizes */
|
||||
.btn-sm {
|
||||
height: var(--button-height-sm);
|
||||
padding-inline: var(--spacing-4);
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
.btn-lg {
|
||||
height: var(--button-height-lg);
|
||||
padding-inline: var(--spacing-8);
|
||||
font-size: var(--font-size-lg);
|
||||
}
|
||||
|
||||
/* Icon-only button */
|
||||
.btn-icon {
|
||||
padding: 0;
|
||||
width: var(--button-height-md);
|
||||
}
|
||||
|
||||
.btn-icon.btn-sm {
|
||||
width: var(--button-height-sm);
|
||||
}
|
||||
|
||||
.btn-icon.btn-lg {
|
||||
width: var(--button-height-lg);
|
||||
}
|
||||
|
||||
/* Disabled state */
|
||||
.btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Loading state */
|
||||
.btn-loading {
|
||||
position: relative;
|
||||
color: transparent;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.btn-loading::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
border: 2px solid currentColor;
|
||||
border-right-color: transparent;
|
||||
border-radius: var(--radius-full);
|
||||
animation: spin 0.6s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ============================================
|
||||
INPUTS & FORMS
|
||||
============================================ */
|
||||
|
||||
/* Input base */
|
||||
.input {
|
||||
/* Layout */
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: var(--input-height-md);
|
||||
padding-inline: var(--spacing-4);
|
||||
|
||||
/* Typography */
|
||||
font-size: var(--font-size-base);
|
||||
line-height: 1.5;
|
||||
color: var(--text);
|
||||
|
||||
/* Appearance */
|
||||
background-color: var(--background);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
|
||||
/* Transitions */
|
||||
transition: var(--transition-colors), border-color var(--duration-fast) var(--ease-out);
|
||||
}
|
||||
|
||||
.input::placeholder {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.input:hover:not(:disabled):not(:focus) {
|
||||
border-color: var(--border-strong);
|
||||
}
|
||||
|
||||
.input:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary);
|
||||
box-shadow: 0 0 0 3px var(--primary-subtle);
|
||||
}
|
||||
|
||||
.input:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
background-color: var(--surface-subtle);
|
||||
}
|
||||
|
||||
/* Input with error */
|
||||
.input-error {
|
||||
border-color: var(--danger);
|
||||
}
|
||||
|
||||
.input-error:focus {
|
||||
border-color: var(--danger);
|
||||
box-shadow: 0 0 0 3px var(--danger-subtle);
|
||||
}
|
||||
|
||||
/* Input sizes */
|
||||
.input-sm {
|
||||
height: var(--input-height-sm);
|
||||
padding-inline: var(--spacing-3);
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
.input-lg {
|
||||
height: var(--input-height-lg);
|
||||
padding-inline: var(--spacing-6);
|
||||
font-size: var(--font-size-lg);
|
||||
}
|
||||
|
||||
/* Textarea */
|
||||
.textarea {
|
||||
min-height: 5rem;
|
||||
padding-block: var(--spacing-3);
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
/* Label */
|
||||
.label {
|
||||
display: block;
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: var(--font-weight-medium);
|
||||
color: var(--text);
|
||||
margin-bottom: var(--spacing-2);
|
||||
}
|
||||
|
||||
.label-required::after {
|
||||
content: ' *';
|
||||
color: var(--danger);
|
||||
}
|
||||
|
||||
/* Helper text */
|
||||
.helper-text {
|
||||
display: block;
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--text-muted);
|
||||
margin-top: var(--spacing-1-5);
|
||||
}
|
||||
|
||||
.helper-text-error {
|
||||
color: var(--danger);
|
||||
}
|
||||
|
||||
/* Form group */
|
||||
.form-group {
|
||||
margin-bottom: var(--spacing-6);
|
||||
}
|
||||
|
||||
/* Select */
|
||||
.select {
|
||||
appearance: none;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath fill='currentColor' d='M4.5 6L8 9.5L11.5 6'/%3E%3C/svg%3E");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right var(--spacing-3) center;
|
||||
padding-right: var(--spacing-10);
|
||||
}
|
||||
|
||||
/* Checkbox & Radio */
|
||||
.checkbox,
|
||||
.radio {
|
||||
appearance: none;
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
border: 2px solid var(--border);
|
||||
border-radius: var(--radius-xs);
|
||||
background-color: var(--background);
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
transition: var(--transition-colors);
|
||||
}
|
||||
|
||||
.radio {
|
||||
border-radius: var(--radius-full);
|
||||
}
|
||||
|
||||
.checkbox:checked,
|
||||
.radio:checked {
|
||||
background-color: var(--primary);
|
||||
border-color: var(--primary);
|
||||
}
|
||||
|
||||
.checkbox:checked::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 5px;
|
||||
width: 4px;
|
||||
height: 8px;
|
||||
border: 2px solid white;
|
||||
border-top: 0;
|
||||
border-left: 0;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.radio:checked::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background-color: white;
|
||||
border-radius: var(--radius-full);
|
||||
}
|
||||
|
||||
|
||||
/* ============================================
|
||||
CARDS
|
||||
============================================ */
|
||||
|
||||
.card {
|
||||
background-color: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: var(--spacing-6);
|
||||
box-shadow: var(--shadow-sm);
|
||||
transition: var(--transition-all);
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
box-shadow: var(--shadow-md);
|
||||
border-color: var(--border-strong);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
margin-bottom: var(--spacing-4);
|
||||
padding-bottom: var(--spacing-4);
|
||||
border-bottom: 1px solid var(--border-subtle);
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: var(--font-size-xl);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--text);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.card-description {
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--text-secondary);
|
||||
margin-top: var(--spacing-2);
|
||||
}
|
||||
|
||||
.card-body {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
margin-top: var(--spacing-4);
|
||||
padding-top: var(--spacing-4);
|
||||
border-top: 1px solid var(--border-subtle);
|
||||
display: flex;
|
||||
gap: var(--spacing-3);
|
||||
}
|
||||
|
||||
/* Card variants */
|
||||
.card-interactive {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.card-interactive:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
|
||||
.card-interactive:active {
|
||||
transform: translateY(0);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
|
||||
/* ============================================
|
||||
BADGES & TAGS
|
||||
============================================ */
|
||||
|
||||
.badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-1);
|
||||
padding: var(--spacing-0-5) var(--spacing-2);
|
||||
font-size: var(--font-size-xs);
|
||||
font-weight: var(--font-weight-medium);
|
||||
line-height: 1;
|
||||
border-radius: var(--radius-xs);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.badge-primary {
|
||||
background-color: var(--primary-subtle);
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.badge-secondary {
|
||||
background-color: var(--secondary-subtle);
|
||||
color: var(--secondary);
|
||||
}
|
||||
|
||||
.badge-success {
|
||||
background-color: var(--success-subtle);
|
||||
color: var(--success);
|
||||
}
|
||||
|
||||
.badge-warning {
|
||||
background-color: var(--warning-subtle);
|
||||
color: var(--warning);
|
||||
}
|
||||
|
||||
.badge-danger {
|
||||
background-color: var(--danger-subtle);
|
||||
color: var(--danger);
|
||||
}
|
||||
|
||||
.badge-outline {
|
||||
background-color: transparent;
|
||||
border: 1px solid var(--border);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
|
||||
/* ============================================
|
||||
ALERTS
|
||||
============================================ */
|
||||
|
||||
.alert {
|
||||
padding: var(--spacing-4);
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid transparent;
|
||||
display: flex;
|
||||
gap: var(--spacing-3);
|
||||
}
|
||||
|
||||
.alert-icon {
|
||||
flex-shrink: 0;
|
||||
width: var(--icon-lg);
|
||||
height: var(--icon-lg);
|
||||
}
|
||||
|
||||
.alert-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.alert-title {
|
||||
font-weight: var(--font-weight-semibold);
|
||||
margin-bottom: var(--spacing-1);
|
||||
}
|
||||
|
||||
.alert-description {
|
||||
font-size: var(--font-size-sm);
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.alert-info {
|
||||
background-color: var(--info-subtle);
|
||||
color: var(--info);
|
||||
border-color: var(--info);
|
||||
}
|
||||
|
||||
.alert-success {
|
||||
background-color: var(--success-subtle);
|
||||
color: var(--success);
|
||||
border-color: var(--success);
|
||||
}
|
||||
|
||||
.alert-warning {
|
||||
background-color: var(--warning-subtle);
|
||||
color: var(--warning);
|
||||
border-color: var(--warning);
|
||||
}
|
||||
|
||||
.alert-danger {
|
||||
background-color: var(--danger-subtle);
|
||||
color: var(--danger);
|
||||
border-color: var(--danger);
|
||||
}
|
||||
|
||||
|
||||
/* ============================================
|
||||
MODALS
|
||||
============================================ */
|
||||
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background-color: var(--overlay);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--spacing-4);
|
||||
z-index: var(--z-modal-backdrop);
|
||||
animation: fade-in var(--duration-base) var(--ease-out);
|
||||
}
|
||||
|
||||
.modal {
|
||||
background-color: var(--surface);
|
||||
border-radius: var(--radius-xl);
|
||||
box-shadow: var(--shadow-2xl);
|
||||
max-width: 32rem;
|
||||
width: 100%;
|
||||
max-height: 90vh;
|
||||
overflow: auto;
|
||||
animation: modal-enter var(--duration-base) var(--ease-out);
|
||||
z-index: var(--z-modal);
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
padding: var(--spacing-6);
|
||||
border-bottom: 1px solid var(--border-subtle);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
font-size: var(--font-size-xl);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--text);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
background: transparent;
|
||||
border: none;
|
||||
padding: var(--spacing-2);
|
||||
cursor: pointer;
|
||||
color: var(--text-muted);
|
||||
border-radius: var(--radius-sm);
|
||||
transition: var(--transition-colors);
|
||||
}
|
||||
|
||||
.modal-close:hover {
|
||||
background-color: var(--surface-hover);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: var(--spacing-6);
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
padding: var(--spacing-6);
|
||||
border-top: 1px solid var(--border-subtle);
|
||||
display: flex;
|
||||
gap: var(--spacing-3);
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
@keyframes fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes modal-enter {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-1rem) scale(0.95);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ============================================
|
||||
TOOLTIPS
|
||||
============================================ */
|
||||
|
||||
.tooltip {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.tooltip-content {
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) translateY(-0.5rem);
|
||||
padding: var(--spacing-2) var(--spacing-3);
|
||||
background-color: var(--text);
|
||||
color: var(--text-inverse);
|
||||
font-size: var(--font-size-sm);
|
||||
border-radius: var(--radius-sm);
|
||||
white-space: nowrap;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: var(--transition-opacity);
|
||||
z-index: var(--z-tooltip);
|
||||
}
|
||||
|
||||
.tooltip:hover .tooltip-content {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.tooltip-content::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
border: 4px solid transparent;
|
||||
border-top-color: var(--text);
|
||||
}
|
||||
|
||||
|
||||
/* ============================================
|
||||
LOADING SKELETON
|
||||
============================================ */
|
||||
|
||||
.skeleton {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
var(--surface-subtle) 0%,
|
||||
var(--surface-hover) 50%,
|
||||
var(--surface-subtle) 100%
|
||||
);
|
||||
background-size: 200% 100%;
|
||||
animation: shimmer 1.5s ease-in-out infinite;
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.skeleton-text {
|
||||
height: 1em;
|
||||
margin-bottom: var(--spacing-2);
|
||||
}
|
||||
|
||||
.skeleton-title {
|
||||
height: 1.5em;
|
||||
width: 60%;
|
||||
margin-bottom: var(--spacing-3);
|
||||
}
|
||||
|
||||
.skeleton-avatar {
|
||||
width: var(--avatar-md);
|
||||
height: var(--avatar-md);
|
||||
border-radius: var(--radius-full);
|
||||
}
|
||||
|
||||
.skeleton-card {
|
||||
height: 12rem;
|
||||
border-radius: var(--radius-lg);
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% {
|
||||
background-position: 200% 0;
|
||||
}
|
||||
100% {
|
||||
background-position: -200% 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ============================================
|
||||
AVATARS
|
||||
============================================ */
|
||||
|
||||
.avatar {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: var(--avatar-md);
|
||||
height: var(--avatar-md);
|
||||
border-radius: var(--radius-full);
|
||||
overflow: hidden;
|
||||
background-color: var(--surface-subtle);
|
||||
color: var(--text);
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
|
||||
.avatar img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.avatar-sm {
|
||||
width: var(--avatar-sm);
|
||||
height: var(--avatar-sm);
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
.avatar-lg {
|
||||
width: var(--avatar-lg);
|
||||
height: var(--avatar-lg);
|
||||
font-size: var(--font-size-xl);
|
||||
}
|
||||
|
||||
|
||||
/* ============================================
|
||||
EMPTY STATES
|
||||
============================================ */
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: var(--spacing-16) var(--spacing-8);
|
||||
}
|
||||
|
||||
.empty-state-icon {
|
||||
width: var(--spacing-16);
|
||||
height: var(--spacing-16);
|
||||
margin: 0 auto var(--spacing-6);
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.empty-state-title {
|
||||
font-size: var(--font-size-xl);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--text);
|
||||
margin-bottom: var(--spacing-2);
|
||||
}
|
||||
|
||||
.empty-state-description {
|
||||
font-size: var(--font-size-base);
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: var(--spacing-6);
|
||||
max-width: 28rem;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.empty-state-action {
|
||||
margin-top: var(--spacing-6);
|
||||
}
|
||||
|
||||
|
||||
/* ============================================
|
||||
ERROR STATES
|
||||
============================================ */
|
||||
|
||||
.error-state {
|
||||
text-align: center;
|
||||
padding: var(--spacing-12) var(--spacing-8);
|
||||
background-color: var(--danger-subtle);
|
||||
border-radius: var(--radius-lg);
|
||||
border: 1px solid var(--danger);
|
||||
}
|
||||
|
||||
.error-state-icon {
|
||||
width: var(--spacing-12);
|
||||
height: var(--spacing-12);
|
||||
margin: 0 auto var(--spacing-4);
|
||||
color: var(--danger);
|
||||
}
|
||||
|
||||
.error-state-title {
|
||||
font-size: var(--font-size-lg);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--danger);
|
||||
margin-bottom: var(--spacing-2);
|
||||
}
|
||||
|
||||
.error-state-message {
|
||||
font-size: var(--font-size-base);
|
||||
color: var(--text);
|
||||
margin-bottom: var(--spacing-4);
|
||||
}
|
||||
|
||||
.error-state-actions {
|
||||
display: flex;
|
||||
gap: var(--spacing-3);
|
||||
justify-content: center;
|
||||
margin-top: var(--spacing-4);
|
||||
}
|
||||
|
||||
|
||||
/* ============================================
|
||||
RESPONSIVE UTILITIES
|
||||
============================================ */
|
||||
|
||||
/* Hide on mobile */
|
||||
@media (max-width: 767px) {
|
||||
.hide-mobile {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Hide on tablet and up */
|
||||
@media (min-width: 768px) {
|
||||
.hide-tablet {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Hide on desktop */
|
||||
@media (min-width: 1024px) {
|
||||
.hide-desktop {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
525
skills/frontend-design/examples/css/tokens.css
Executable file
525
skills/frontend-design/examples/css/tokens.css
Executable file
@@ -0,0 +1,525 @@
|
||||
/**
|
||||
* Design Tokens — Complete CSS Custom Properties System
|
||||
*
|
||||
* This file provides a comprehensive design token system for consistent UI development.
|
||||
* It includes semantic color slots, typography scales, spacing, radius, shadows, and motion.
|
||||
*
|
||||
* Usage:
|
||||
* 1. Import this file in your globals.css: @import './tokens.css';
|
||||
* 2. Reference tokens using var(--token-name)
|
||||
* 3. Override in [data-theme="dark"] for dark mode
|
||||
*
|
||||
* Location: {project_path}/skills/frontend-design/examples/css/tokens.css
|
||||
*/
|
||||
|
||||
/* ============================================
|
||||
COLOR SYSTEM - Semantic Tokens
|
||||
============================================ */
|
||||
|
||||
:root {
|
||||
/* Background & Surfaces */
|
||||
--background: oklch(99% 0 0); /* Main page background */
|
||||
--surface: oklch(100% 0 0); /* Card/panel background */
|
||||
--surface-subtle: oklch(98% 0.005 250); /* Subtle surface variant */
|
||||
--surface-hover: oklch(97% 0.01 250); /* Hover state for surfaces */
|
||||
|
||||
/* Text Colors */
|
||||
--text: oklch(20% 0.01 250); /* Primary text */
|
||||
--text-secondary: oklch(45% 0.015 250); /* Secondary text */
|
||||
--text-muted: oklch(60% 0.01 250); /* Muted/helper text */
|
||||
--text-inverse: oklch(98% 0 0); /* Text on dark backgrounds */
|
||||
|
||||
/* Borders */
|
||||
--border: oklch(90% 0.005 250); /* Standard borders */
|
||||
--border-subtle: oklch(95% 0.003 250); /* Subtle dividers */
|
||||
--border-strong: oklch(75% 0.01 250); /* Emphasized borders */
|
||||
|
||||
/* Primary Brand */
|
||||
--primary: oklch(55% 0.18 250); /* Primary brand color */
|
||||
--primary-hover: oklch(50% 0.20 250); /* Primary hover state */
|
||||
--primary-active: oklch(45% 0.22 250); /* Primary active/pressed */
|
||||
--primary-subtle: oklch(95% 0.03 250); /* Primary tint background */
|
||||
--primary-muted: oklch(85% 0.08 250); /* Primary muted variant */
|
||||
--primary-foreground: oklch(98% 0.01 250); /* Text on primary */
|
||||
|
||||
/* Secondary */
|
||||
--secondary: oklch(65% 0.08 280); /* Secondary accent */
|
||||
--secondary-hover: oklch(60% 0.10 280); /* Secondary hover */
|
||||
--secondary-active: oklch(55% 0.12 280); /* Secondary active */
|
||||
--secondary-subtle: oklch(95% 0.02 280); /* Secondary tint */
|
||||
--secondary-foreground: oklch(98% 0.01 280); /* Text on secondary */
|
||||
|
||||
/* Accent */
|
||||
--accent: oklch(70% 0.15 160); /* Accent highlights */
|
||||
--accent-hover: oklch(65% 0.17 160); /* Accent hover */
|
||||
--accent-foreground: oklch(10% 0.01 160); /* Text on accent */
|
||||
|
||||
/* Semantic Colors */
|
||||
--success: oklch(65% 0.15 145); /* Success states */
|
||||
--success-subtle: oklch(95% 0.03 145); /* Success background */
|
||||
--success-foreground: oklch(98% 0.01 145); /* Text on success */
|
||||
|
||||
--warning: oklch(75% 0.15 85); /* Warning states */
|
||||
--warning-subtle: oklch(95% 0.05 85); /* Warning background */
|
||||
--warning-foreground: oklch(15% 0.02 85); /* Text on warning */
|
||||
|
||||
--danger: oklch(60% 0.20 25); /* Error/danger states */
|
||||
--danger-subtle: oklch(95% 0.04 25); /* Error background */
|
||||
--danger-foreground: oklch(98% 0.01 25); /* Text on danger */
|
||||
|
||||
--info: oklch(65% 0.12 230); /* Info states */
|
||||
--info-subtle: oklch(95% 0.02 230); /* Info background */
|
||||
--info-foreground: oklch(98% 0.01 230); /* Text on info */
|
||||
|
||||
/* Overlays & Scrim */
|
||||
--overlay: oklch(0% 0 0 / 0.5); /* Modal overlay */
|
||||
--scrim: oklch(0% 0 0 / 0.3); /* Backdrop scrim */
|
||||
}
|
||||
|
||||
/* Dark Mode Color Adjustments */
|
||||
[data-theme="dark"] {
|
||||
/* Background & Surfaces */
|
||||
--background: oklch(15% 0.01 250);
|
||||
--surface: oklch(20% 0.015 250);
|
||||
--surface-subtle: oklch(25% 0.02 250);
|
||||
--surface-hover: oklch(30% 0.025 250);
|
||||
|
||||
/* Text Colors */
|
||||
--text: oklch(95% 0.01 250);
|
||||
--text-secondary: oklch(70% 0.015 250);
|
||||
--text-muted: oklch(55% 0.01 250);
|
||||
--text-inverse: oklch(15% 0 0);
|
||||
|
||||
/* Borders */
|
||||
--border: oklch(35% 0.01 250);
|
||||
--border-subtle: oklch(25% 0.005 250);
|
||||
--border-strong: oklch(50% 0.015 250);
|
||||
|
||||
/* Primary Brand (adjusted for dark) */
|
||||
--primary: oklch(65% 0.18 250);
|
||||
--primary-hover: oklch(70% 0.20 250);
|
||||
--primary-active: oklch(75% 0.22 250);
|
||||
--primary-subtle: oklch(25% 0.08 250);
|
||||
--primary-muted: oklch(35% 0.12 250);
|
||||
--primary-foreground: oklch(10% 0.01 250);
|
||||
|
||||
/* Secondary */
|
||||
--secondary: oklch(70% 0.08 280);
|
||||
--secondary-hover: oklch(75% 0.10 280);
|
||||
--secondary-active: oklch(80% 0.12 280);
|
||||
--secondary-subtle: oklch(25% 0.04 280);
|
||||
--secondary-foreground: oklch(10% 0.01 280);
|
||||
|
||||
/* Accent */
|
||||
--accent: oklch(75% 0.15 160);
|
||||
--accent-hover: oklch(80% 0.17 160);
|
||||
--accent-foreground: oklch(10% 0.01 160);
|
||||
|
||||
/* Semantic Colors (adjusted) */
|
||||
--success: oklch(70% 0.15 145);
|
||||
--success-subtle: oklch(22% 0.06 145);
|
||||
|
||||
--warning: oklch(80% 0.15 85);
|
||||
--warning-subtle: oklch(25% 0.08 85);
|
||||
|
||||
--danger: oklch(65% 0.20 25);
|
||||
--danger-subtle: oklch(22% 0.08 25);
|
||||
|
||||
--info: oklch(70% 0.12 230);
|
||||
--info-subtle: oklch(22% 0.05 230);
|
||||
|
||||
/* Overlays */
|
||||
--overlay: oklch(0% 0 0 / 0.7);
|
||||
--scrim: oklch(0% 0 0 / 0.5);
|
||||
}
|
||||
|
||||
|
||||
/* ============================================
|
||||
TYPOGRAPHY SCALE
|
||||
============================================ */
|
||||
|
||||
:root {
|
||||
/* Font Families */
|
||||
--font-sans: 'Inter', system-ui, -apple-system, sans-serif;
|
||||
--font-serif: 'Merriweather', Georgia, serif;
|
||||
--font-mono: 'JetBrains Mono', 'Fira Code', monospace;
|
||||
|
||||
/* Font Sizes - Using clamp() for fluid typography */
|
||||
--font-size-xs: clamp(0.75rem, 0.7rem + 0.15vw, 0.875rem); /* 12-14px */
|
||||
--font-size-sm: clamp(0.875rem, 0.8rem + 0.2vw, 1rem); /* 14-16px */
|
||||
--font-size-base: clamp(1rem, 0.95rem + 0.25vw, 1.125rem); /* 16-18px */
|
||||
--font-size-lg: clamp(1.125rem, 1.05rem + 0.3vw, 1.25rem); /* 18-20px */
|
||||
--font-size-xl: clamp(1.25rem, 1.15rem + 0.4vw, 1.5rem); /* 20-24px */
|
||||
--font-size-2xl: clamp(1.5rem, 1.35rem + 0.6vw, 2rem); /* 24-32px */
|
||||
--font-size-3xl: clamp(1.875rem, 1.65rem + 0.9vw, 2.5rem); /* 30-40px */
|
||||
--font-size-4xl: clamp(2.25rem, 1.95rem + 1.2vw, 3.5rem); /* 36-56px */
|
||||
--font-size-5xl: clamp(3rem, 2.5rem + 2vw, 4.5rem); /* 48-72px */
|
||||
|
||||
/* Line Heights */
|
||||
--line-height-none: 1;
|
||||
--line-height-tight: 1.25;
|
||||
--line-height-snug: 1.375;
|
||||
--line-height-normal: 1.5;
|
||||
--line-height-relaxed: 1.625;
|
||||
--line-height-loose: 2;
|
||||
|
||||
/* Font Weights */
|
||||
--font-weight-light: 300;
|
||||
--font-weight-normal: 400;
|
||||
--font-weight-medium: 500;
|
||||
--font-weight-semibold: 600;
|
||||
--font-weight-bold: 700;
|
||||
--font-weight-extrabold: 800;
|
||||
|
||||
/* Letter Spacing */
|
||||
--letter-spacing-tighter: -0.05em;
|
||||
--letter-spacing-tight: -0.025em;
|
||||
--letter-spacing-normal: 0;
|
||||
--letter-spacing-wide: 0.025em;
|
||||
--letter-spacing-wider: 0.05em;
|
||||
--letter-spacing-widest: 0.1em;
|
||||
}
|
||||
|
||||
/* Typography Presets */
|
||||
.text-display {
|
||||
font-size: var(--font-size-5xl);
|
||||
line-height: var(--line-height-tight);
|
||||
font-weight: var(--font-weight-extrabold);
|
||||
letter-spacing: var(--letter-spacing-tighter);
|
||||
}
|
||||
|
||||
.text-h1 {
|
||||
font-size: var(--font-size-4xl);
|
||||
line-height: var(--line-height-tight);
|
||||
font-weight: var(--font-weight-bold);
|
||||
letter-spacing: var(--letter-spacing-tight);
|
||||
}
|
||||
|
||||
.text-h2 {
|
||||
font-size: var(--font-size-3xl);
|
||||
line-height: var(--line-height-snug);
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
.text-h3 {
|
||||
font-size: var(--font-size-2xl);
|
||||
line-height: var(--line-height-snug);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
.text-h4 {
|
||||
font-size: var(--font-size-xl);
|
||||
line-height: var(--line-height-normal);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
.text-body {
|
||||
font-size: var(--font-size-base);
|
||||
line-height: var(--line-height-relaxed);
|
||||
font-weight: var(--font-weight-normal);
|
||||
}
|
||||
|
||||
.text-small {
|
||||
font-size: var(--font-size-sm);
|
||||
line-height: var(--line-height-normal);
|
||||
font-weight: var(--font-weight-normal);
|
||||
}
|
||||
|
||||
.text-caption {
|
||||
font-size: var(--font-size-xs);
|
||||
line-height: var(--line-height-normal);
|
||||
font-weight: var(--font-weight-normal);
|
||||
letter-spacing: var(--letter-spacing-wide);
|
||||
}
|
||||
|
||||
|
||||
/* ============================================
|
||||
SPACING SCALE (8px System)
|
||||
============================================ */
|
||||
|
||||
:root {
|
||||
--spacing-0: 0;
|
||||
--spacing-px: 1px;
|
||||
--spacing-0-5: 0.125rem; /* 2px */
|
||||
--spacing-1: 0.25rem; /* 4px */
|
||||
--spacing-1-5: 0.375rem; /* 6px */
|
||||
--spacing-2: 0.5rem; /* 8px */
|
||||
--spacing-2-5: 0.625rem; /* 10px */
|
||||
--spacing-3: 0.75rem; /* 12px */
|
||||
--spacing-4: 1rem; /* 16px */
|
||||
--spacing-5: 1.25rem; /* 20px */
|
||||
--spacing-6: 1.5rem; /* 24px */
|
||||
--spacing-7: 1.75rem; /* 28px */
|
||||
--spacing-8: 2rem; /* 32px */
|
||||
--spacing-9: 2.25rem; /* 36px */
|
||||
--spacing-10: 2.5rem; /* 40px */
|
||||
--spacing-12: 3rem; /* 48px */
|
||||
--spacing-14: 3.5rem; /* 56px */
|
||||
--spacing-16: 4rem; /* 64px */
|
||||
--spacing-20: 5rem; /* 80px */
|
||||
--spacing-24: 6rem; /* 96px */
|
||||
--spacing-28: 7rem; /* 112px */
|
||||
--spacing-32: 8rem; /* 128px */
|
||||
--spacing-36: 9rem; /* 144px */
|
||||
--spacing-40: 10rem; /* 160px */
|
||||
--spacing-48: 12rem; /* 192px */
|
||||
--spacing-56: 14rem; /* 224px */
|
||||
--spacing-64: 16rem; /* 256px */
|
||||
}
|
||||
|
||||
|
||||
/* ============================================
|
||||
BORDER RADIUS SCALE
|
||||
============================================ */
|
||||
|
||||
:root {
|
||||
--radius-none: 0;
|
||||
--radius-xs: 0.125rem; /* 2px - badges, tags */
|
||||
--radius-sm: 0.25rem; /* 4px - buttons, small inputs */
|
||||
--radius-md: 0.375rem; /* 6px - inputs, cards */
|
||||
--radius-lg: 0.5rem; /* 8px - large cards */
|
||||
--radius-xl: 0.75rem; /* 12px - modals, panels */
|
||||
--radius-2xl: 1rem; /* 16px - hero sections */
|
||||
--radius-3xl: 1.5rem; /* 24px - special elements */
|
||||
--radius-full: 9999px; /* Pills, avatars, circles */
|
||||
}
|
||||
|
||||
|
||||
/* ============================================
|
||||
SHADOW SCALE
|
||||
============================================ */
|
||||
|
||||
:root {
|
||||
/* Light Mode Shadows */
|
||||
--shadow-xs: 0 1px 2px 0 oklch(0% 0 0 / 0.05);
|
||||
--shadow-sm: 0 1px 3px 0 oklch(0% 0 0 / 0.1),
|
||||
0 1px 2px -1px oklch(0% 0 0 / 0.1);
|
||||
--shadow-md: 0 4px 6px -1px oklch(0% 0 0 / 0.1),
|
||||
0 2px 4px -2px oklch(0% 0 0 / 0.1);
|
||||
--shadow-lg: 0 10px 15px -3px oklch(0% 0 0 / 0.1),
|
||||
0 4px 6px -4px oklch(0% 0 0 / 0.1);
|
||||
--shadow-xl: 0 20px 25px -5px oklch(0% 0 0 / 0.1),
|
||||
0 8px 10px -6px oklch(0% 0 0 / 0.1);
|
||||
--shadow-2xl: 0 25px 50px -12px oklch(0% 0 0 / 0.25);
|
||||
|
||||
/* Colored Shadows (for accents) */
|
||||
--shadow-primary: 0 4px 12px -2px var(--primary),
|
||||
0 2px 6px -2px var(--primary);
|
||||
--shadow-secondary: 0 4px 12px -2px var(--secondary),
|
||||
0 2px 6px -2px var(--secondary);
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
/* Stronger shadows for dark mode */
|
||||
--shadow-xs: 0 1px 2px 0 oklch(0% 0 0 / 0.3);
|
||||
--shadow-sm: 0 1px 3px 0 oklch(0% 0 0 / 0.4),
|
||||
0 1px 2px -1px oklch(0% 0 0 / 0.3);
|
||||
--shadow-md: 0 4px 6px -1px oklch(0% 0 0 / 0.4),
|
||||
0 2px 4px -2px oklch(0% 0 0 / 0.3);
|
||||
--shadow-lg: 0 10px 15px -3px oklch(0% 0 0 / 0.5),
|
||||
0 4px 6px -4px oklch(0% 0 0 / 0.4);
|
||||
--shadow-xl: 0 20px 25px -5px oklch(0% 0 0 / 0.5),
|
||||
0 8px 10px -6px oklch(0% 0 0 / 0.4);
|
||||
--shadow-2xl: 0 25px 50px -12px oklch(0% 0 0 / 0.6);
|
||||
}
|
||||
|
||||
|
||||
/* ============================================
|
||||
MOTION TOKENS
|
||||
============================================ */
|
||||
|
||||
:root {
|
||||
/* Duration */
|
||||
--duration-instant: 0ms;
|
||||
--duration-fast: 150ms;
|
||||
--duration-base: 220ms;
|
||||
--duration-slow: 300ms;
|
||||
--duration-slower: 400ms;
|
||||
|
||||
/* Easing Functions */
|
||||
--ease-in: cubic-bezier(0.4, 0, 1, 1);
|
||||
--ease-out: cubic-bezier(0, 0, 0.2, 1);
|
||||
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
--ease-bounce: cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
||||
|
||||
/* Common Transitions */
|
||||
--transition-colors: color var(--duration-fast) var(--ease-out),
|
||||
background-color var(--duration-fast) var(--ease-out),
|
||||
border-color var(--duration-fast) var(--ease-out);
|
||||
--transition-transform: transform var(--duration-base) var(--ease-out);
|
||||
--transition-opacity: opacity var(--duration-base) var(--ease-out);
|
||||
--transition-all: all var(--duration-base) var(--ease-in-out);
|
||||
}
|
||||
|
||||
/* Respect Reduced Motion Preference */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
:root {
|
||||
--duration-instant: 0ms;
|
||||
--duration-fast: 0ms;
|
||||
--duration-base: 0ms;
|
||||
--duration-slow: 0ms;
|
||||
--duration-slower: 0ms;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ============================================
|
||||
LAYOUT & CONTAINER
|
||||
============================================ */
|
||||
|
||||
:root {
|
||||
/* Container widths */
|
||||
--container-sm: 640px;
|
||||
--container-md: 768px;
|
||||
--container-lg: 1024px;
|
||||
--container-xl: 1280px;
|
||||
--container-2xl: 1536px;
|
||||
|
||||
/* Breakpoints (for reference in JS) */
|
||||
--breakpoint-sm: 640px;
|
||||
--breakpoint-md: 768px;
|
||||
--breakpoint-lg: 1024px;
|
||||
--breakpoint-xl: 1280px;
|
||||
--breakpoint-2xl: 1536px;
|
||||
|
||||
/* Z-index scale */
|
||||
--z-base: 0;
|
||||
--z-dropdown: 1000;
|
||||
--z-sticky: 1100;
|
||||
--z-fixed: 1200;
|
||||
--z-modal-backdrop: 1300;
|
||||
--z-modal: 1400;
|
||||
--z-popover: 1500;
|
||||
--z-tooltip: 1600;
|
||||
--z-notification: 1700;
|
||||
--z-max: 9999;
|
||||
}
|
||||
|
||||
|
||||
/* ============================================
|
||||
COMPONENT DENSITY
|
||||
============================================ */
|
||||
|
||||
:root {
|
||||
/* Button heights */
|
||||
--button-height-sm: 2.25rem; /* 36px */
|
||||
--button-height-md: 2.75rem; /* 44px */
|
||||
--button-height-lg: 3rem; /* 48px */
|
||||
|
||||
/* Input heights */
|
||||
--input-height-sm: 2.25rem; /* 36px */
|
||||
--input-height-md: 2.5rem; /* 40px */
|
||||
--input-height-lg: 3rem; /* 48px */
|
||||
|
||||
/* Icon sizes */
|
||||
--icon-xs: 0.75rem; /* 12px */
|
||||
--icon-sm: 1rem; /* 16px */
|
||||
--icon-md: 1.25rem; /* 20px */
|
||||
--icon-lg: 1.5rem; /* 24px */
|
||||
--icon-xl: 2rem; /* 32px */
|
||||
--icon-2xl: 2.5rem; /* 40px */
|
||||
|
||||
/* Avatar sizes */
|
||||
--avatar-xs: 1.5rem; /* 24px */
|
||||
--avatar-sm: 2rem; /* 32px */
|
||||
--avatar-md: 2.5rem; /* 40px */
|
||||
--avatar-lg: 3rem; /* 48px */
|
||||
--avatar-xl: 4rem; /* 64px */
|
||||
--avatar-2xl: 6rem; /* 96px */
|
||||
}
|
||||
|
||||
|
||||
/* ============================================
|
||||
FOCUS & ACCESSIBILITY
|
||||
============================================ */
|
||||
|
||||
:root {
|
||||
--focus-ring-width: 2px;
|
||||
--focus-ring-offset: 2px;
|
||||
--focus-ring-color: var(--primary);
|
||||
}
|
||||
|
||||
/* Default focus style */
|
||||
:focus-visible {
|
||||
outline: var(--focus-ring-width) solid var(--focus-ring-color);
|
||||
outline-offset: var(--focus-ring-offset);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
/* Remove default outline, apply custom */
|
||||
*:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
*:focus-visible {
|
||||
outline: var(--focus-ring-width) solid var(--focus-ring-color);
|
||||
outline-offset: var(--focus-ring-offset);
|
||||
}
|
||||
|
||||
|
||||
/* ============================================
|
||||
USAGE EXAMPLES
|
||||
============================================ */
|
||||
|
||||
/*
|
||||
Example 1: Button with tokens
|
||||
.button {
|
||||
height: var(--button-height-md);
|
||||
padding-inline: var(--spacing-6);
|
||||
background-color: var(--primary);
|
||||
color: var(--primary-foreground);
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: var(--font-size-base);
|
||||
font-weight: var(--font-weight-medium);
|
||||
transition: var(--transition-colors);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background-color: var(--primary-hover);
|
||||
}
|
||||
|
||||
.button:active {
|
||||
background-color: var(--primary-active);
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
Example 2: Card with tokens
|
||||
.card {
|
||||
background-color: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: var(--spacing-6);
|
||||
box-shadow: var(--shadow-sm);
|
||||
transition: var(--transition-all);
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
box-shadow: var(--shadow-md);
|
||||
border-color: var(--border-strong);
|
||||
}
|
||||
|
||||
Example 3: Typography with tokens
|
||||
.heading {
|
||||
font-size: var(--font-size-3xl);
|
||||
line-height: var(--line-height-tight);
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--text);
|
||||
margin-bottom: var(--spacing-4);
|
||||
}
|
||||
|
||||
.paragraph {
|
||||
font-size: var(--font-size-base);
|
||||
line-height: var(--line-height-relaxed);
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: var(--spacing-6);
|
||||
}
|
||||
*/
|
||||
616
skills/frontend-design/examples/typescript/design-tokens.ts
Executable file
616
skills/frontend-design/examples/typescript/design-tokens.ts
Executable file
@@ -0,0 +1,616 @@
|
||||
/**
|
||||
* Design Tokens — Type-Safe Token Definitions
|
||||
*
|
||||
* This file provides TypeScript interfaces and types for the design token system.
|
||||
* Use these to ensure type safety when working with design tokens programmatically.
|
||||
*
|
||||
* Location: {project_path}/skills/frontend-design/examples/typescript/design-tokens.ts
|
||||
*/
|
||||
|
||||
// ============================================
|
||||
// Color Tokens
|
||||
// ============================================
|
||||
|
||||
export interface ColorTokens {
|
||||
// Background & Surfaces
|
||||
background: string;
|
||||
surface: string;
|
||||
surfaceSubtle: string;
|
||||
surfaceHover: string;
|
||||
|
||||
// Text
|
||||
text: string;
|
||||
textSecondary: string;
|
||||
textMuted: string;
|
||||
textInverse: string;
|
||||
|
||||
// Borders
|
||||
border: string;
|
||||
borderSubtle: string;
|
||||
borderStrong: string;
|
||||
|
||||
// Primary
|
||||
primary: string;
|
||||
primaryHover: string;
|
||||
primaryActive: string;
|
||||
primarySubtle: string;
|
||||
primaryMuted: string;
|
||||
primaryForeground: string;
|
||||
|
||||
// Secondary
|
||||
secondary: string;
|
||||
secondaryHover: string;
|
||||
secondaryActive: string;
|
||||
secondarySubtle: string;
|
||||
secondaryForeground: string;
|
||||
|
||||
// Accent
|
||||
accent: string;
|
||||
accentHover: string;
|
||||
accentForeground: string;
|
||||
|
||||
// Semantic Colors
|
||||
success: string;
|
||||
successSubtle: string;
|
||||
successForeground: string;
|
||||
|
||||
warning: string;
|
||||
warningSubtle: string;
|
||||
warningForeground: string;
|
||||
|
||||
danger: string;
|
||||
dangerSubtle: string;
|
||||
dangerForeground: string;
|
||||
|
||||
info: string;
|
||||
infoSubtle: string;
|
||||
infoForeground: string;
|
||||
|
||||
// Overlays
|
||||
overlay: string;
|
||||
scrim: string;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Typography Tokens
|
||||
// ============================================
|
||||
|
||||
export interface TypographyToken {
|
||||
fontSize: string;
|
||||
lineHeight: string;
|
||||
fontWeight: number;
|
||||
letterSpacing?: string;
|
||||
}
|
||||
|
||||
export interface TypographyTokens {
|
||||
display: TypographyToken;
|
||||
h1: TypographyToken;
|
||||
h2: TypographyToken;
|
||||
h3: TypographyToken;
|
||||
h4: TypographyToken;
|
||||
body: TypographyToken;
|
||||
bodySmall: TypographyToken;
|
||||
caption: TypographyToken;
|
||||
mono: TypographyToken;
|
||||
}
|
||||
|
||||
// Font families
|
||||
export interface FontFamilies {
|
||||
sans: string;
|
||||
serif: string;
|
||||
mono: string;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Spacing Tokens
|
||||
// ============================================
|
||||
|
||||
export interface SpacingTokens {
|
||||
0: string;
|
||||
px: string;
|
||||
0.5: string;
|
||||
1: string;
|
||||
1.5: string;
|
||||
2: string;
|
||||
2.5: string;
|
||||
3: string;
|
||||
4: string;
|
||||
5: string;
|
||||
6: string;
|
||||
7: string;
|
||||
8: string;
|
||||
9: string;
|
||||
10: string;
|
||||
12: string;
|
||||
14: string;
|
||||
16: string;
|
||||
20: string;
|
||||
24: string;
|
||||
28: string;
|
||||
32: string;
|
||||
36: string;
|
||||
40: string;
|
||||
48: string;
|
||||
56: string;
|
||||
64: string;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Radius Tokens
|
||||
// ============================================
|
||||
|
||||
export interface RadiusTokens {
|
||||
none: string;
|
||||
xs: string;
|
||||
sm: string;
|
||||
md: string;
|
||||
lg: string;
|
||||
xl: string;
|
||||
'2xl': string;
|
||||
'3xl': string;
|
||||
full: string;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Shadow Tokens
|
||||
// ============================================
|
||||
|
||||
export interface ShadowTokens {
|
||||
xs: string;
|
||||
sm: string;
|
||||
md: string;
|
||||
lg: string;
|
||||
xl: string;
|
||||
'2xl': string;
|
||||
primary: string;
|
||||
secondary: string;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Motion Tokens
|
||||
// ============================================
|
||||
|
||||
export interface MotionTokens {
|
||||
// Durations
|
||||
instant: string;
|
||||
fast: string;
|
||||
base: string;
|
||||
slow: string;
|
||||
slower: string;
|
||||
|
||||
// Easings
|
||||
easeIn: string;
|
||||
easeOut: string;
|
||||
easeInOut: string;
|
||||
easeBounce: string;
|
||||
|
||||
// Common transitions
|
||||
transitionColors: string;
|
||||
transitionTransform: string;
|
||||
transitionOpacity: string;
|
||||
transitionAll: string;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Component Size Tokens
|
||||
// ============================================
|
||||
|
||||
export interface ComponentSizeTokens {
|
||||
button: {
|
||||
sm: string;
|
||||
md: string;
|
||||
lg: string;
|
||||
};
|
||||
input: {
|
||||
sm: string;
|
||||
md: string;
|
||||
lg: string;
|
||||
};
|
||||
icon: {
|
||||
xs: string;
|
||||
sm: string;
|
||||
md: string;
|
||||
lg: string;
|
||||
xl: string;
|
||||
'2xl': string;
|
||||
};
|
||||
avatar: {
|
||||
xs: string;
|
||||
sm: string;
|
||||
md: string;
|
||||
lg: string;
|
||||
xl: string;
|
||||
'2xl': string;
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Z-Index Tokens
|
||||
// ============================================
|
||||
|
||||
export interface ZIndexTokens {
|
||||
base: number;
|
||||
dropdown: number;
|
||||
sticky: number;
|
||||
fixed: number;
|
||||
modalBackdrop: number;
|
||||
modal: number;
|
||||
popover: number;
|
||||
tooltip: number;
|
||||
notification: number;
|
||||
max: number;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Complete Design System
|
||||
// ============================================
|
||||
|
||||
export interface DesignTokens {
|
||||
colors: ColorTokens;
|
||||
typography: TypographyTokens;
|
||||
fontFamilies: FontFamilies;
|
||||
spacing: SpacingTokens;
|
||||
radius: RadiusTokens;
|
||||
shadows: ShadowTokens;
|
||||
motion: MotionTokens;
|
||||
componentSizes: ComponentSizeTokens;
|
||||
zIndex: ZIndexTokens;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Theme Type
|
||||
// ============================================
|
||||
|
||||
export type Theme = 'light' | 'dark' | 'system';
|
||||
|
||||
export interface ThemeConfig {
|
||||
theme: Theme;
|
||||
tokens: DesignTokens;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Token Helpers
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* Get CSS variable name for a token
|
||||
* @param tokenPath - Dot-separated path to token (e.g., 'colors.primary')
|
||||
* @returns CSS variable name (e.g., '--primary')
|
||||
*/
|
||||
export function getCSSVariable(tokenPath: string): string {
|
||||
const parts = tokenPath.split('.');
|
||||
const tokenName = parts[parts.length - 1];
|
||||
|
||||
// Convert camelCase to kebab-case
|
||||
const kebabCase = tokenName.replace(/([A-Z])/g, '-$1').toLowerCase();
|
||||
|
||||
return `var(--${kebabCase})`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get token value from design system
|
||||
* @param tokens - Design tokens object
|
||||
* @param path - Dot-separated path to token
|
||||
* @returns Token value or undefined
|
||||
*/
|
||||
export function getTokenValue(tokens: DesignTokens, path: string): string | undefined {
|
||||
const parts = path.split('.');
|
||||
let value: any = tokens;
|
||||
|
||||
for (const part of parts) {
|
||||
if (value && typeof value === 'object' && part in value) {
|
||||
value = value[part];
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
return typeof value === 'string' ? value : undefined;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Default Light Theme Tokens
|
||||
// ============================================
|
||||
|
||||
export const lightThemeTokens: DesignTokens = {
|
||||
colors: {
|
||||
background: 'oklch(99% 0 0)',
|
||||
surface: 'oklch(100% 0 0)',
|
||||
surfaceSubtle: 'oklch(98% 0.005 250)',
|
||||
surfaceHover: 'oklch(97% 0.01 250)',
|
||||
|
||||
text: 'oklch(20% 0.01 250)',
|
||||
textSecondary: 'oklch(45% 0.015 250)',
|
||||
textMuted: 'oklch(60% 0.01 250)',
|
||||
textInverse: 'oklch(98% 0 0)',
|
||||
|
||||
border: 'oklch(90% 0.005 250)',
|
||||
borderSubtle: 'oklch(95% 0.003 250)',
|
||||
borderStrong: 'oklch(75% 0.01 250)',
|
||||
|
||||
primary: 'oklch(55% 0.18 250)',
|
||||
primaryHover: 'oklch(50% 0.20 250)',
|
||||
primaryActive: 'oklch(45% 0.22 250)',
|
||||
primarySubtle: 'oklch(95% 0.03 250)',
|
||||
primaryMuted: 'oklch(85% 0.08 250)',
|
||||
primaryForeground: 'oklch(98% 0.01 250)',
|
||||
|
||||
secondary: 'oklch(65% 0.08 280)',
|
||||
secondaryHover: 'oklch(60% 0.10 280)',
|
||||
secondaryActive: 'oklch(55% 0.12 280)',
|
||||
secondarySubtle: 'oklch(95% 0.02 280)',
|
||||
secondaryForeground: 'oklch(98% 0.01 280)',
|
||||
|
||||
accent: 'oklch(70% 0.15 160)',
|
||||
accentHover: 'oklch(65% 0.17 160)',
|
||||
accentForeground: 'oklch(10% 0.01 160)',
|
||||
|
||||
success: 'oklch(65% 0.15 145)',
|
||||
successSubtle: 'oklch(95% 0.03 145)',
|
||||
successForeground: 'oklch(98% 0.01 145)',
|
||||
|
||||
warning: 'oklch(75% 0.15 85)',
|
||||
warningSubtle: 'oklch(95% 0.05 85)',
|
||||
warningForeground: 'oklch(15% 0.02 85)',
|
||||
|
||||
danger: 'oklch(60% 0.20 25)',
|
||||
dangerSubtle: 'oklch(95% 0.04 25)',
|
||||
dangerForeground: 'oklch(98% 0.01 25)',
|
||||
|
||||
info: 'oklch(65% 0.12 230)',
|
||||
infoSubtle: 'oklch(95% 0.02 230)',
|
||||
infoForeground: 'oklch(98% 0.01 230)',
|
||||
|
||||
overlay: 'oklch(0% 0 0 / 0.5)',
|
||||
scrim: 'oklch(0% 0 0 / 0.3)',
|
||||
},
|
||||
|
||||
typography: {
|
||||
display: {
|
||||
fontSize: 'clamp(3rem, 2.5rem + 2vw, 4.5rem)',
|
||||
lineHeight: '1.1',
|
||||
fontWeight: 800,
|
||||
letterSpacing: '-0.05em',
|
||||
},
|
||||
h1: {
|
||||
fontSize: 'clamp(2.25rem, 1.95rem + 1.2vw, 3.5rem)',
|
||||
lineHeight: '1.2',
|
||||
fontWeight: 700,
|
||||
letterSpacing: '-0.025em',
|
||||
},
|
||||
h2: {
|
||||
fontSize: 'clamp(1.875rem, 1.65rem + 0.9vw, 2.5rem)',
|
||||
lineHeight: '1.3',
|
||||
fontWeight: 700,
|
||||
},
|
||||
h3: {
|
||||
fontSize: 'clamp(1.5rem, 1.35rem + 0.6vw, 2rem)',
|
||||
lineHeight: '1.4',
|
||||
fontWeight: 600,
|
||||
},
|
||||
h4: {
|
||||
fontSize: 'clamp(1.25rem, 1.15rem + 0.4vw, 1.5rem)',
|
||||
lineHeight: '1.5',
|
||||
fontWeight: 600,
|
||||
},
|
||||
body: {
|
||||
fontSize: 'clamp(1rem, 0.95rem + 0.25vw, 1.125rem)',
|
||||
lineHeight: '1.6',
|
||||
fontWeight: 400,
|
||||
},
|
||||
bodySmall: {
|
||||
fontSize: 'clamp(0.875rem, 0.8rem + 0.2vw, 1rem)',
|
||||
lineHeight: '1.5',
|
||||
fontWeight: 400,
|
||||
},
|
||||
caption: {
|
||||
fontSize: 'clamp(0.75rem, 0.7rem + 0.15vw, 0.875rem)',
|
||||
lineHeight: '1.4',
|
||||
fontWeight: 400,
|
||||
letterSpacing: '0.025em',
|
||||
},
|
||||
mono: {
|
||||
fontSize: '0.875rem',
|
||||
lineHeight: '1.5',
|
||||
fontWeight: 400,
|
||||
},
|
||||
},
|
||||
|
||||
fontFamilies: {
|
||||
sans: "'Inter', system-ui, -apple-system, sans-serif",
|
||||
serif: "'Merriweather', Georgia, serif",
|
||||
mono: "'JetBrains Mono', 'Fira Code', monospace",
|
||||
},
|
||||
|
||||
spacing: {
|
||||
0: '0',
|
||||
px: '1px',
|
||||
0.5: '0.125rem',
|
||||
1: '0.25rem',
|
||||
1.5: '0.375rem',
|
||||
2: '0.5rem',
|
||||
2.5: '0.625rem',
|
||||
3: '0.75rem',
|
||||
4: '1rem',
|
||||
5: '1.25rem',
|
||||
6: '1.5rem',
|
||||
7: '1.75rem',
|
||||
8: '2rem',
|
||||
9: '2.25rem',
|
||||
10: '2.5rem',
|
||||
12: '3rem',
|
||||
14: '3.5rem',
|
||||
16: '4rem',
|
||||
20: '5rem',
|
||||
24: '6rem',
|
||||
28: '7rem',
|
||||
32: '8rem',
|
||||
36: '9rem',
|
||||
40: '10rem',
|
||||
48: '12rem',
|
||||
56: '14rem',
|
||||
64: '16rem',
|
||||
},
|
||||
|
||||
radius: {
|
||||
none: '0',
|
||||
xs: '0.125rem',
|
||||
sm: '0.25rem',
|
||||
md: '0.375rem',
|
||||
lg: '0.5rem',
|
||||
xl: '0.75rem',
|
||||
'2xl': '1rem',
|
||||
'3xl': '1.5rem',
|
||||
full: '9999px',
|
||||
},
|
||||
|
||||
shadows: {
|
||||
xs: '0 1px 2px 0 oklch(0% 0 0 / 0.05)',
|
||||
sm: '0 1px 3px 0 oklch(0% 0 0 / 0.1), 0 1px 2px -1px oklch(0% 0 0 / 0.1)',
|
||||
md: '0 4px 6px -1px oklch(0% 0 0 / 0.1), 0 2px 4px -2px oklch(0% 0 0 / 0.1)',
|
||||
lg: '0 10px 15px -3px oklch(0% 0 0 / 0.1), 0 4px 6px -4px oklch(0% 0 0 / 0.1)',
|
||||
xl: '0 20px 25px -5px oklch(0% 0 0 / 0.1), 0 8px 10px -6px oklch(0% 0 0 / 0.1)',
|
||||
'2xl': '0 25px 50px -12px oklch(0% 0 0 / 0.25)',
|
||||
primary: '0 4px 12px -2px oklch(55% 0.18 250), 0 2px 6px -2px oklch(55% 0.18 250)',
|
||||
secondary: '0 4px 12px -2px oklch(65% 0.08 280), 0 2px 6px -2px oklch(65% 0.08 280)',
|
||||
},
|
||||
|
||||
motion: {
|
||||
instant: '0ms',
|
||||
fast: '150ms',
|
||||
base: '220ms',
|
||||
slow: '300ms',
|
||||
slower: '400ms',
|
||||
|
||||
easeIn: 'cubic-bezier(0.4, 0, 1, 1)',
|
||||
easeOut: 'cubic-bezier(0, 0, 0.2, 1)',
|
||||
easeInOut: 'cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
easeBounce: 'cubic-bezier(0.68, -0.55, 0.265, 1.55)',
|
||||
|
||||
transitionColors: 'color 150ms cubic-bezier(0, 0, 0.2, 1), background-color 150ms cubic-bezier(0, 0, 0.2, 1), border-color 150ms cubic-bezier(0, 0, 0.2, 1)',
|
||||
transitionTransform: 'transform 220ms cubic-bezier(0, 0, 0.2, 1)',
|
||||
transitionOpacity: 'opacity 220ms cubic-bezier(0, 0, 0.2, 1)',
|
||||
transitionAll: 'all 220ms cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
},
|
||||
|
||||
componentSizes: {
|
||||
button: {
|
||||
sm: '2.25rem',
|
||||
md: '2.75rem',
|
||||
lg: '3rem',
|
||||
},
|
||||
input: {
|
||||
sm: '2.25rem',
|
||||
md: '2.5rem',
|
||||
lg: '3rem',
|
||||
},
|
||||
icon: {
|
||||
xs: '0.75rem',
|
||||
sm: '1rem',
|
||||
md: '1.25rem',
|
||||
lg: '1.5rem',
|
||||
xl: '2rem',
|
||||
'2xl': '2.5rem',
|
||||
},
|
||||
avatar: {
|
||||
xs: '1.5rem',
|
||||
sm: '2rem',
|
||||
md: '2.5rem',
|
||||
lg: '3rem',
|
||||
xl: '4rem',
|
||||
'2xl': '6rem',
|
||||
},
|
||||
},
|
||||
|
||||
zIndex: {
|
||||
base: 0,
|
||||
dropdown: 1000,
|
||||
sticky: 1100,
|
||||
fixed: 1200,
|
||||
modalBackdrop: 1300,
|
||||
modal: 1400,
|
||||
popover: 1500,
|
||||
tooltip: 1600,
|
||||
notification: 1700,
|
||||
max: 9999,
|
||||
},
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// Dark Theme Tokens (Overrides only)
|
||||
// ============================================
|
||||
|
||||
export const darkThemeColorOverrides: Partial<ColorTokens> = {
|
||||
background: 'oklch(15% 0.01 250)',
|
||||
surface: 'oklch(20% 0.015 250)',
|
||||
surfaceSubtle: 'oklch(25% 0.02 250)',
|
||||
surfaceHover: 'oklch(30% 0.025 250)',
|
||||
|
||||
text: 'oklch(95% 0.01 250)',
|
||||
textSecondary: 'oklch(70% 0.015 250)',
|
||||
textMuted: 'oklch(55% 0.01 250)',
|
||||
textInverse: 'oklch(15% 0 0)',
|
||||
|
||||
border: 'oklch(35% 0.01 250)',
|
||||
borderSubtle: 'oklch(25% 0.005 250)',
|
||||
borderStrong: 'oklch(50% 0.015 250)',
|
||||
|
||||
primary: 'oklch(65% 0.18 250)',
|
||||
primaryHover: 'oklch(70% 0.20 250)',
|
||||
primaryActive: 'oklch(75% 0.22 250)',
|
||||
primarySubtle: 'oklch(25% 0.08 250)',
|
||||
primaryMuted: 'oklch(35% 0.12 250)',
|
||||
primaryForeground: 'oklch(10% 0.01 250)',
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// Type Guards
|
||||
// ============================================
|
||||
|
||||
export function isValidTheme(theme: string): theme is Theme {
|
||||
return ['light', 'dark', 'system'].includes(theme);
|
||||
}
|
||||
|
||||
export function isColorToken(token: any): token is keyof ColorTokens {
|
||||
return typeof token === 'string' && token in lightThemeTokens.colors;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Utility Functions
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* Merge default tokens with custom overrides
|
||||
*/
|
||||
export function mergeTokens(
|
||||
base: DesignTokens,
|
||||
overrides: Partial<DesignTokens>
|
||||
): DesignTokens {
|
||||
return {
|
||||
...base,
|
||||
...overrides,
|
||||
colors: { ...base.colors, ...(overrides.colors || {}) },
|
||||
typography: { ...base.typography, ...(overrides.typography || {}) },
|
||||
spacing: { ...base.spacing, ...(overrides.spacing || {}) },
|
||||
radius: { ...base.radius, ...(overrides.radius || {}) },
|
||||
shadows: { ...base.shadows, ...(overrides.shadows || {}) },
|
||||
motion: { ...base.motion, ...(overrides.motion || {}) },
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate token value format
|
||||
*/
|
||||
export function validateTokenValue(value: string, type: 'color' | 'spacing' | 'radius' | 'shadow'): boolean {
|
||||
switch (type) {
|
||||
case 'color':
|
||||
return /^(oklch|rgb|hsl|#)/.test(value);
|
||||
case 'spacing':
|
||||
case 'radius':
|
||||
return /^\d+(\.\d+)?(px|rem|em|%)$/.test(value);
|
||||
case 'shadow':
|
||||
return value.includes('oklch') || value.includes('rgb') || value === 'none';
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
685
skills/frontend-design/examples/typescript/sample-components.tsx
Executable file
685
skills/frontend-design/examples/typescript/sample-components.tsx
Executable file
@@ -0,0 +1,685 @@
|
||||
/**
|
||||
* Sample Components — Production-Ready React Components
|
||||
*
|
||||
* This file demonstrates how to build type-safe, accessible React components
|
||||
* using the design token system.
|
||||
*
|
||||
* Location: {project_path}/skills/frontend-design/examples/typescript/sample-components.tsx
|
||||
*
|
||||
* All components include:
|
||||
* - Full TypeScript type safety
|
||||
* - Complete state coverage (default/hover/active/focus/disabled/loading/error)
|
||||
* - Accessibility (ARIA labels, keyboard navigation)
|
||||
* - Responsive design
|
||||
* - Token-based styling
|
||||
*/
|
||||
|
||||
import React, { useState, forwardRef, InputHTMLAttributes, ButtonHTMLAttributes } from 'react';
|
||||
import { cn } from './utils'; // Utility for classname merging
|
||||
|
||||
// ============================================
|
||||
// BUTTON COMPONENT
|
||||
// ============================================
|
||||
|
||||
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
variant?: 'primary' | 'secondary' | 'outline' | 'ghost' | 'danger';
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
isLoading?: boolean;
|
||||
leftIcon?: React.ReactNode;
|
||||
rightIcon?: React.ReactNode;
|
||||
}
|
||||
|
||||
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
(
|
||||
{
|
||||
variant = 'primary',
|
||||
size = 'md',
|
||||
isLoading = false,
|
||||
disabled,
|
||||
leftIcon,
|
||||
rightIcon,
|
||||
children,
|
||||
className,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const baseClasses = 'btn';
|
||||
const variantClasses = {
|
||||
primary: 'btn-primary',
|
||||
secondary: 'btn-secondary',
|
||||
outline: 'btn-outline',
|
||||
ghost: 'btn-ghost',
|
||||
danger: 'btn-danger',
|
||||
};
|
||||
const sizeClasses = {
|
||||
sm: 'btn-sm',
|
||||
md: '',
|
||||
lg: 'btn-lg',
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
ref={ref}
|
||||
className={cn(
|
||||
baseClasses,
|
||||
variantClasses[variant],
|
||||
sizeClasses[size],
|
||||
isLoading && 'btn-loading',
|
||||
className
|
||||
)}
|
||||
disabled={disabled || isLoading}
|
||||
{...props}
|
||||
>
|
||||
{!isLoading && leftIcon && <span className="btn-icon-left">{leftIcon}</span>}
|
||||
<span className={isLoading ? 'opacity-0' : ''}>{children}</span>
|
||||
{!isLoading && rightIcon && <span className="btn-icon-right">{rightIcon}</span>}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
Button.displayName = 'Button';
|
||||
|
||||
// Usage Example:
|
||||
/*
|
||||
<Button variant="primary" size="md">
|
||||
Click me
|
||||
</Button>
|
||||
|
||||
<Button variant="outline" isLoading>
|
||||
Loading...
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="primary"
|
||||
leftIcon={<PlusIcon />}
|
||||
rightIcon={<ArrowRightIcon />}
|
||||
>
|
||||
Get Started
|
||||
</Button>
|
||||
*/
|
||||
|
||||
// ============================================
|
||||
// INPUT COMPONENT
|
||||
// ============================================
|
||||
|
||||
interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
|
||||
label?: string;
|
||||
error?: string;
|
||||
helperText?: string;
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
leftIcon?: React.ReactNode;
|
||||
rightIcon?: React.ReactNode;
|
||||
}
|
||||
|
||||
export const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
(
|
||||
{
|
||||
label,
|
||||
error,
|
||||
helperText,
|
||||
size = 'md',
|
||||
leftIcon,
|
||||
rightIcon,
|
||||
className,
|
||||
id,
|
||||
required,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const inputId = id || `input-${Math.random().toString(36).substr(2, 9)}`;
|
||||
const errorId = error ? `${inputId}-error` : undefined;
|
||||
const helperId = helperText ? `${inputId}-helper` : undefined;
|
||||
|
||||
const sizeClasses = {
|
||||
sm: 'input-sm',
|
||||
md: '',
|
||||
lg: 'input-lg',
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="form-group">
|
||||
{label && (
|
||||
<label htmlFor={inputId} className={cn('label', required && 'label-required')}>
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
|
||||
<div className="relative">
|
||||
{leftIcon && (
|
||||
<div className="absolute left-3 top-1/2 -translate-y-1/2 text-text-muted">
|
||||
{leftIcon}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<input
|
||||
ref={ref}
|
||||
id={inputId}
|
||||
className={cn(
|
||||
'input',
|
||||
sizeClasses[size],
|
||||
error && 'input-error',
|
||||
leftIcon && 'pl-10',
|
||||
rightIcon && 'pr-10',
|
||||
className
|
||||
)}
|
||||
aria-invalid={error ? 'true' : 'false'}
|
||||
aria-describedby={cn(errorId, helperId)}
|
||||
{...props}
|
||||
/>
|
||||
|
||||
{rightIcon && (
|
||||
<div className="absolute right-3 top-1/2 -translate-y-1/2 text-text-muted">
|
||||
{rightIcon}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<span id={errorId} className="helper-text helper-text-error" role="alert">
|
||||
{error}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{!error && helperText && (
|
||||
<span id={helperId} className="helper-text">
|
||||
{helperText}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
Input.displayName = 'Input';
|
||||
|
||||
// Usage Example:
|
||||
/*
|
||||
<Input
|
||||
label="Email"
|
||||
type="email"
|
||||
placeholder="you@example.com"
|
||||
required
|
||||
leftIcon={<MailIcon />}
|
||||
/>
|
||||
|
||||
<Input
|
||||
label="Password"
|
||||
type="password"
|
||||
error="Password must be at least 8 characters"
|
||||
helperText="Use a strong, unique password"
|
||||
/>
|
||||
*/
|
||||
|
||||
// ============================================
|
||||
// CARD COMPONENT
|
||||
// ============================================
|
||||
|
||||
interface CardProps {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
interactive?: boolean;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
export function Card({ children, className, interactive = false, onClick }: CardProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'card',
|
||||
interactive && 'card-interactive',
|
||||
className
|
||||
)}
|
||||
onClick={onClick}
|
||||
role={interactive ? 'button' : undefined}
|
||||
tabIndex={interactive ? 0 : undefined}
|
||||
onKeyDown={
|
||||
interactive
|
||||
? (e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
onClick?.();
|
||||
}
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function CardHeader({ children, className }: { children: React.ReactNode; className?: string }) {
|
||||
return <div className={cn('card-header', className)}>{children}</div>;
|
||||
}
|
||||
|
||||
export function CardTitle({ children, className }: { children: React.ReactNode; className?: string }) {
|
||||
return <h3 className={cn('card-title', className)}>{children}</h3>;
|
||||
}
|
||||
|
||||
export function CardDescription({ children, className }: { children: React.ReactNode; className?: string }) {
|
||||
return <p className={cn('card-description', className)}>{children}</p>;
|
||||
}
|
||||
|
||||
export function CardBody({ children, className }: { children: React.ReactNode; className?: string }) {
|
||||
return <div className={cn('card-body', className)}>{children}</div>;
|
||||
}
|
||||
|
||||
export function CardFooter({ children, className }: { children: React.ReactNode; className?: string }) {
|
||||
return <div className={cn('card-footer', className)}>{children}</div>;
|
||||
}
|
||||
|
||||
// Usage Example:
|
||||
/*
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Project Overview</CardTitle>
|
||||
<CardDescription>Track your project's progress</CardDescription>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<p>Your project is 75% complete</p>
|
||||
</CardBody>
|
||||
<CardFooter>
|
||||
<Button variant="primary">View Details</Button>
|
||||
<Button variant="outline">Share</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
|
||||
<Card interactive onClick={() => console.log('Clicked')}>
|
||||
<CardBody>Click me!</CardBody>
|
||||
</Card>
|
||||
*/
|
||||
|
||||
// ============================================
|
||||
// BADGE COMPONENT
|
||||
// ============================================
|
||||
|
||||
interface BadgeProps {
|
||||
children: React.ReactNode;
|
||||
variant?: 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'outline';
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function Badge({ children, variant = 'primary', className }: BadgeProps) {
|
||||
const variantClasses = {
|
||||
primary: 'badge-primary',
|
||||
secondary: 'badge-secondary',
|
||||
success: 'badge-success',
|
||||
warning: 'badge-warning',
|
||||
danger: 'badge-danger',
|
||||
outline: 'badge-outline',
|
||||
};
|
||||
|
||||
return (
|
||||
<span className={cn('badge', variantClasses[variant], className)}>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
// Usage Example:
|
||||
/*
|
||||
<Badge variant="success">Active</Badge>
|
||||
<Badge variant="warning">Pending</Badge>
|
||||
<Badge variant="danger">Failed</Badge>
|
||||
*/
|
||||
|
||||
// ============================================
|
||||
// ALERT COMPONENT
|
||||
// ============================================
|
||||
|
||||
interface AlertProps {
|
||||
children: React.ReactNode;
|
||||
variant?: 'info' | 'success' | 'warning' | 'danger';
|
||||
title?: string;
|
||||
onClose?: () => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function Alert({ children, variant = 'info', title, onClose, className }: AlertProps) {
|
||||
const variantClasses = {
|
||||
info: 'alert-info',
|
||||
success: 'alert-success',
|
||||
warning: 'alert-warning',
|
||||
danger: 'alert-danger',
|
||||
};
|
||||
|
||||
const icons = {
|
||||
info: (
|
||||
<svg className="alert-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
),
|
||||
success: (
|
||||
<svg className="alert-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
),
|
||||
warning: (
|
||||
<svg className="alert-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
||||
</svg>
|
||||
),
|
||||
danger: (
|
||||
<svg className="alert-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
),
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={cn('alert', variantClasses[variant], className)} role="alert">
|
||||
{icons[variant]}
|
||||
<div className="alert-content">
|
||||
{title && <div className="alert-title">{title}</div>}
|
||||
<div className="alert-description">{children}</div>
|
||||
</div>
|
||||
{onClose && (
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="ml-auto text-current hover:opacity-70 transition-opacity"
|
||||
aria-label="Close alert"
|
||||
>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<line x1="18" y1="6" x2="6" y2="18" />
|
||||
<line x1="6" y1="6" x2="18" y2="18" />
|
||||
</svg>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Usage Example:
|
||||
/*
|
||||
<Alert variant="success" title="Success">
|
||||
Your changes have been saved successfully.
|
||||
</Alert>
|
||||
|
||||
<Alert variant="danger" title="Error" onClose={() => console.log('Closed')}>
|
||||
Failed to save changes. Please try again.
|
||||
</Alert>
|
||||
*/
|
||||
|
||||
// ============================================
|
||||
// MODAL COMPONENT
|
||||
// ============================================
|
||||
|
||||
interface ModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
children: React.ReactNode;
|
||||
title?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function Modal({ isOpen, onClose, children, title, className }: ModalProps) {
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<div className="modal-overlay" onClick={onClose}>
|
||||
<div
|
||||
className={cn('modal', className)}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby={title ? 'modal-title' : undefined}
|
||||
>
|
||||
{title && (
|
||||
<div className="modal-header">
|
||||
<h2 id="modal-title" className="modal-title">
|
||||
{title}
|
||||
</h2>
|
||||
<button onClick={onClose} className="modal-close" aria-label="Close modal">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<line x1="18" y1="6" x2="6" y2="18" />
|
||||
<line x1="6" y1="6" x2="18" y2="18" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ModalBody({ children, className }: { children: React.ReactNode; className?: string }) {
|
||||
return <div className={cn('modal-body', className)}>{children}</div>;
|
||||
}
|
||||
|
||||
export function ModalFooter({ children, className }: { children: React.ReactNode; className?: string }) {
|
||||
return <div className={cn('modal-footer', className)}>{children}</div>;
|
||||
}
|
||||
|
||||
// Usage Example:
|
||||
/*
|
||||
function Example() {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button onClick={() => setIsOpen(true)}>Open Modal</Button>
|
||||
|
||||
<Modal isOpen={isOpen} onClose={() => setIsOpen(false)} title="Confirm Action">
|
||||
<ModalBody>
|
||||
<p>Are you sure you want to proceed with this action?</p>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button variant="outline" onClick={() => setIsOpen(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="primary" onClick={() => setIsOpen(false)}>
|
||||
Confirm
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
*/
|
||||
|
||||
// ============================================
|
||||
// SKELETON LOADING COMPONENT
|
||||
// ============================================
|
||||
|
||||
interface SkeletonProps {
|
||||
className?: string;
|
||||
variant?: 'text' | 'title' | 'avatar' | 'card' | 'rect';
|
||||
width?: string;
|
||||
height?: string;
|
||||
}
|
||||
|
||||
export function Skeleton({ className, variant = 'text', width, height }: SkeletonProps) {
|
||||
const variantClasses = {
|
||||
text: 'skeleton-text',
|
||||
title: 'skeleton-title',
|
||||
avatar: 'skeleton-avatar',
|
||||
card: 'skeleton-card',
|
||||
rect: '',
|
||||
};
|
||||
|
||||
const style: React.CSSProperties = {};
|
||||
if (width) style.width = width;
|
||||
if (height) style.height = height;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn('skeleton', variantClasses[variant], className)}
|
||||
style={style}
|
||||
aria-label="Loading"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// Usage Example:
|
||||
/*
|
||||
// Loading card
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<Skeleton variant="title" />
|
||||
<Skeleton variant="text" width="60%" />
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<Skeleton variant="text" />
|
||||
<Skeleton variant="text" />
|
||||
<Skeleton variant="text" width="80%" />
|
||||
</CardBody>
|
||||
</Card>
|
||||
|
||||
// Loading list
|
||||
<div className="space-y-4">
|
||||
{[1, 2, 3].map((i) => (
|
||||
<div key={i} className="flex items-center gap-4">
|
||||
<Skeleton variant="avatar" />
|
||||
<div className="flex-1">
|
||||
<Skeleton variant="text" width="40%" />
|
||||
<Skeleton variant="text" width="60%" />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
*/
|
||||
|
||||
// ============================================
|
||||
// EMPTY STATE COMPONENT
|
||||
// ============================================
|
||||
|
||||
interface EmptyStateProps {
|
||||
icon?: React.ReactNode;
|
||||
title: string;
|
||||
description?: string;
|
||||
action?: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function EmptyState({ icon, title, description, action, className }: EmptyStateProps) {
|
||||
return (
|
||||
<div className={cn('empty-state', className)}>
|
||||
{icon && <div className="empty-state-icon">{icon}</div>}
|
||||
<h3 className="empty-state-title">{title}</h3>
|
||||
{description && <p className="empty-state-description">{description}</p>}
|
||||
{action && <div className="empty-state-action">{action}</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Usage Example:
|
||||
/*
|
||||
<EmptyState
|
||||
icon={<FolderIcon />}
|
||||
title="No projects yet"
|
||||
description="Get started by creating your first project"
|
||||
action={
|
||||
<Button variant="primary" leftIcon={<PlusIcon />}>
|
||||
Create Project
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
*/
|
||||
|
||||
// ============================================
|
||||
// ERROR STATE COMPONENT
|
||||
// ============================================
|
||||
|
||||
interface ErrorStateProps {
|
||||
title: string;
|
||||
message: string;
|
||||
onRetry?: () => void;
|
||||
onGoBack?: () => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function ErrorState({ title, message, onRetry, onGoBack, className }: ErrorStateProps) {
|
||||
return (
|
||||
<div className={cn('error-state', className)}>
|
||||
<svg className="error-state-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
|
||||
/>
|
||||
</svg>
|
||||
<h3 className="error-state-title">{title}</h3>
|
||||
<p className="error-state-message">{message}</p>
|
||||
<div className="error-state-actions">
|
||||
{onGoBack && (
|
||||
<Button variant="outline" onClick={onGoBack}>
|
||||
Go Back
|
||||
</Button>
|
||||
)}
|
||||
{onRetry && (
|
||||
<Button variant="primary" onClick={onRetry}>
|
||||
Try Again
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Usage Example:
|
||||
/*
|
||||
<ErrorState
|
||||
title="Failed to load data"
|
||||
message="We couldn't load your projects. Please check your connection and try again."
|
||||
onRetry={() => refetch()}
|
||||
onGoBack={() => navigate('/')}
|
||||
/>
|
||||
*/
|
||||
|
||||
// ============================================
|
||||
// AVATAR COMPONENT
|
||||
// ============================================
|
||||
|
||||
interface AvatarProps {
|
||||
src?: string;
|
||||
alt?: string;
|
||||
fallback?: string;
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function Avatar({ src, alt, fallback, size = 'md', className }: AvatarProps) {
|
||||
const [imageError, setImageError] = useState(false);
|
||||
|
||||
const sizeClasses = {
|
||||
sm: 'avatar-sm',
|
||||
md: '',
|
||||
lg: 'avatar-lg',
|
||||
};
|
||||
|
||||
const showFallback = !src || imageError;
|
||||
const initials = fallback
|
||||
? fallback
|
||||
.split(' ')
|
||||
.map((n) => n[0])
|
||||
.join('')
|
||||
.toUpperCase()
|
||||
.slice(0, 2)
|
||||
: '?';
|
||||
|
||||
return (
|
||||
<div className={cn('avatar', sizeClasses[size], className)}>
|
||||
{showFallback ? (
|
||||
<span>{initials}</span>
|
||||
) : (
|
||||
<img
|
||||
src={src}
|
||||
alt={alt || fallback || 'Avatar'}
|
||||
onError={() => setImageError(true)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Usage Example:
|
||||
/*
|
||||
<Avatar src="/user.jpg" alt="John Doe" fallback="John Doe" />
|
||||
<Avatar fallback="Jane Smith" size="lg" />
|
||||
<Avatar src="/broken.jpg" fallback="Error" /> // Falls back to initials
|
||||
*/
|
||||
399
skills/frontend-design/examples/typescript/theme-provider.tsx
Executable file
399
skills/frontend-design/examples/typescript/theme-provider.tsx
Executable file
@@ -0,0 +1,399 @@
|
||||
/**
|
||||
* Theme Provider — Theme Management System
|
||||
*
|
||||
* This file provides a complete theme management system with:
|
||||
* - Light/dark/system theme support
|
||||
* - Theme persistence (localStorage)
|
||||
* - System preference detection
|
||||
* - Type-safe theme context
|
||||
*
|
||||
* Location: {project_path}/skills/frontend-design/examples/typescript/theme-provider.tsx
|
||||
*
|
||||
* Usage:
|
||||
* ```tsx
|
||||
* import { ThemeProvider, useTheme } from './theme-provider';
|
||||
*
|
||||
* function App() {
|
||||
* return (
|
||||
* <ThemeProvider defaultTheme="system">
|
||||
* <YourApp />
|
||||
* </ThemeProvider>
|
||||
* );
|
||||
* }
|
||||
*
|
||||
* function ThemeToggle() {
|
||||
* const { theme, setTheme } = useTheme();
|
||||
* return <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>Toggle</button>;
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
|
||||
import React, { createContext, useContext, useEffect, useState, ReactNode } from 'react';
|
||||
import { Theme } from './design-tokens';
|
||||
|
||||
// ============================================
|
||||
// Types
|
||||
// ============================================
|
||||
|
||||
interface ThemeProviderProps {
|
||||
children: ReactNode;
|
||||
defaultTheme?: Theme;
|
||||
storageKey?: string;
|
||||
}
|
||||
|
||||
interface ThemeContextType {
|
||||
theme: Theme;
|
||||
effectiveTheme: 'light' | 'dark'; // Resolved theme (system → light/dark)
|
||||
setTheme: (theme: Theme) => void;
|
||||
toggleTheme: () => void;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Context
|
||||
// ============================================
|
||||
|
||||
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
|
||||
|
||||
// ============================================
|
||||
// Provider Component
|
||||
// ============================================
|
||||
|
||||
export function ThemeProvider({
|
||||
children,
|
||||
defaultTheme = 'system',
|
||||
storageKey = 'ui-theme',
|
||||
}: ThemeProviderProps) {
|
||||
const [theme, setThemeState] = useState<Theme>(() => {
|
||||
// Load theme from localStorage if available
|
||||
if (typeof window !== 'undefined') {
|
||||
const stored = localStorage.getItem(storageKey);
|
||||
if (stored && ['light', 'dark', 'system'].includes(stored)) {
|
||||
return stored as Theme;
|
||||
}
|
||||
}
|
||||
return defaultTheme;
|
||||
});
|
||||
|
||||
const [effectiveTheme, setEffectiveTheme] = useState<'light' | 'dark'>(() => {
|
||||
if (theme === 'system') {
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
||||
}
|
||||
return theme;
|
||||
});
|
||||
|
||||
// Update effective theme when theme or system preference changes
|
||||
useEffect(() => {
|
||||
const root = window.document.documentElement;
|
||||
|
||||
// Remove previous theme classes
|
||||
root.classList.remove('light', 'dark');
|
||||
|
||||
if (theme === 'system') {
|
||||
const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
? 'dark'
|
||||
: 'light';
|
||||
root.classList.add(systemTheme);
|
||||
root.setAttribute('data-theme', systemTheme);
|
||||
setEffectiveTheme(systemTheme);
|
||||
} else {
|
||||
root.classList.add(theme);
|
||||
root.setAttribute('data-theme', theme);
|
||||
setEffectiveTheme(theme);
|
||||
}
|
||||
}, [theme]);
|
||||
|
||||
// Listen for system theme changes when in system mode
|
||||
useEffect(() => {
|
||||
if (theme !== 'system') return;
|
||||
|
||||
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
|
||||
const handleChange = (e: MediaQueryListEvent) => {
|
||||
const systemTheme = e.matches ? 'dark' : 'light';
|
||||
const root = window.document.documentElement;
|
||||
root.classList.remove('light', 'dark');
|
||||
root.classList.add(systemTheme);
|
||||
root.setAttribute('data-theme', systemTheme);
|
||||
setEffectiveTheme(systemTheme);
|
||||
};
|
||||
|
||||
// Modern browsers
|
||||
if (mediaQuery.addEventListener) {
|
||||
mediaQuery.addEventListener('change', handleChange);
|
||||
return () => mediaQuery.removeEventListener('change', handleChange);
|
||||
}
|
||||
// Legacy browsers
|
||||
else if (mediaQuery.addListener) {
|
||||
mediaQuery.addListener(handleChange);
|
||||
return () => mediaQuery.removeListener(handleChange);
|
||||
}
|
||||
}, [theme]);
|
||||
|
||||
const setTheme = (newTheme: Theme) => {
|
||||
localStorage.setItem(storageKey, newTheme);
|
||||
setThemeState(newTheme);
|
||||
};
|
||||
|
||||
const toggleTheme = () => {
|
||||
setTheme(effectiveTheme === 'light' ? 'dark' : 'light');
|
||||
};
|
||||
|
||||
const value: ThemeContextType = {
|
||||
theme,
|
||||
effectiveTheme,
|
||||
setTheme,
|
||||
toggleTheme,
|
||||
};
|
||||
|
||||
return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Hook
|
||||
// ============================================
|
||||
|
||||
export function useTheme(): ThemeContextType {
|
||||
const context = useContext(ThemeContext);
|
||||
|
||||
if (context === undefined) {
|
||||
throw new Error('useTheme must be used within a ThemeProvider');
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Theme Toggle Component
|
||||
// ============================================
|
||||
|
||||
interface ThemeToggleProps {
|
||||
className?: string;
|
||||
iconSize?: number;
|
||||
}
|
||||
|
||||
export function ThemeToggle({ className = '', iconSize = 20 }: ThemeToggleProps) {
|
||||
const { theme, effectiveTheme, setTheme } = useTheme();
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={() => setTheme(effectiveTheme === 'light' ? 'dark' : 'light')}
|
||||
className={`btn btn-ghost btn-icon ${className}`}
|
||||
aria-label="Toggle theme"
|
||||
title="Toggle theme"
|
||||
>
|
||||
{effectiveTheme === 'light' ? (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={iconSize}
|
||||
height={iconSize}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
|
||||
</svg>
|
||||
) : (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={iconSize}
|
||||
height={iconSize}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<circle cx="12" cy="12" r="5" />
|
||||
<line x1="12" y1="1" x2="12" y2="3" />
|
||||
<line x1="12" y1="21" x2="12" y2="23" />
|
||||
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
|
||||
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
|
||||
<line x1="1" y1="12" x2="3" y2="12" />
|
||||
<line x1="21" y1="12" x2="23" y2="12" />
|
||||
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
|
||||
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Theme Selector Component (Dropdown)
|
||||
// ============================================
|
||||
|
||||
interface ThemeSelectorProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function ThemeSelector({ className = '' }: ThemeSelectorProps) {
|
||||
const { theme, setTheme } = useTheme();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const themes: { value: Theme; label: string; icon: JSX.Element }[] = [
|
||||
{
|
||||
value: 'light',
|
||||
label: 'Light',
|
||||
icon: (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
>
|
||||
<circle cx="12" cy="12" r="5" />
|
||||
<line x1="12" y1="1" x2="12" y2="3" />
|
||||
<line x1="12" y1="21" x2="12" y2="23" />
|
||||
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
|
||||
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
|
||||
<line x1="1" y1="12" x2="3" y2="12" />
|
||||
<line x1="21" y1="12" x2="23" y2="12" />
|
||||
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
|
||||
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
value: 'dark',
|
||||
label: 'Dark',
|
||||
icon: (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
>
|
||||
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
value: 'system',
|
||||
label: 'System',
|
||||
icon: (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
>
|
||||
<rect x="2" y="3" width="20" height="14" rx="2" />
|
||||
<line x1="8" y1="21" x2="16" y2="21" />
|
||||
<line x1="12" y1="17" x2="12" y2="21" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className={`relative ${className}`}>
|
||||
<button
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
className="btn btn-outline flex items-center gap-2"
|
||||
aria-label="Select theme"
|
||||
>
|
||||
{themes.find((t) => t.value === theme)?.icon}
|
||||
<span>{themes.find((t) => t.value === theme)?.label}</span>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
className={`transition-transform ${isOpen ? 'rotate-180' : ''}`}
|
||||
>
|
||||
<polyline points="6 9 12 15 18 9" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{isOpen && (
|
||||
<>
|
||||
<div
|
||||
className="fixed inset-0 z-40"
|
||||
onClick={() => setIsOpen(false)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<div className="absolute right-0 mt-2 w-48 rounded-md shadow-lg bg-surface border border-border z-50">
|
||||
<div className="py-1" role="menu">
|
||||
{themes.map((t) => (
|
||||
<button
|
||||
key={t.value}
|
||||
onClick={() => {
|
||||
setTheme(t.value);
|
||||
setIsOpen(false);
|
||||
}}
|
||||
className={`
|
||||
w-full flex items-center gap-3 px-4 py-2 text-sm
|
||||
hover:bg-surface-hover transition-colors
|
||||
${theme === t.value ? 'bg-surface-subtle font-medium' : ''}
|
||||
`}
|
||||
role="menuitem"
|
||||
>
|
||||
{t.icon}
|
||||
<span>{t.label}</span>
|
||||
{theme === t.value && (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
className="ml-auto"
|
||||
>
|
||||
<polyline points="20 6 9 17 4 12" />
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Utility: Apply theme class to body
|
||||
// ============================================
|
||||
|
||||
export function useThemeClass() {
|
||||
const { effectiveTheme } = useTheme();
|
||||
|
||||
useEffect(() => {
|
||||
const root = window.document.documentElement;
|
||||
root.classList.remove('light', 'dark');
|
||||
root.classList.add(effectiveTheme);
|
||||
root.setAttribute('data-theme', effectiveTheme);
|
||||
}, [effectiveTheme]);
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Higher-Order Component for Theme
|
||||
// ============================================
|
||||
|
||||
export function withTheme<P extends object>(
|
||||
Component: React.ComponentType<P & { theme: ThemeContextType }>
|
||||
) {
|
||||
return function ThemedComponent(props: P) {
|
||||
const theme = useTheme();
|
||||
return <Component {...props} theme={theme} />;
|
||||
};
|
||||
}
|
||||
354
skills/frontend-design/examples/typescript/utils.ts
Executable file
354
skills/frontend-design/examples/typescript/utils.ts
Executable file
@@ -0,0 +1,354 @@
|
||||
/**
|
||||
* Utility Functions
|
||||
*
|
||||
* Location: {project_path}/skills/frontend-design/examples/typescript/utils.ts
|
||||
*/
|
||||
|
||||
import { type ClassValue, clsx } from 'clsx';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
/**
|
||||
* Merge Tailwind CSS classes with proper precedence
|
||||
* Combines clsx and tailwind-merge for optimal class handling
|
||||
*
|
||||
* @param inputs - Class values to merge
|
||||
* @returns Merged class string
|
||||
*
|
||||
* @example
|
||||
* cn('px-4 py-2', 'px-6') // => 'py-2 px-6' (px-6 overwrites px-4)
|
||||
* cn('text-red-500', condition && 'text-blue-500') // => conditional classes
|
||||
*/
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
|
||||
/**
|
||||
* Format file size in human-readable format
|
||||
*
|
||||
* @param bytes - File size in bytes
|
||||
* @param decimals - Number of decimal places (default: 2)
|
||||
* @returns Formatted string (e.g., "1.5 MB")
|
||||
*/
|
||||
export function formatFileSize(bytes: number, decimals: number = 2): string {
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
|
||||
const k = 1024;
|
||||
const dm = decimals < 0 ? 0 : decimals;
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
|
||||
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Debounce function to limit execution rate
|
||||
*
|
||||
* @param func - Function to debounce
|
||||
* @param wait - Wait time in milliseconds
|
||||
* @returns Debounced function
|
||||
*/
|
||||
export function debounce<T extends (...args: any[]) => any>(
|
||||
func: T,
|
||||
wait: number
|
||||
): (...args: Parameters<T>) => void {
|
||||
let timeout: NodeJS.Timeout | null = null;
|
||||
|
||||
return function executedFunction(...args: Parameters<T>) {
|
||||
const later = () => {
|
||||
timeout = null;
|
||||
func(...args);
|
||||
};
|
||||
|
||||
if (timeout) clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Throttle function to limit execution frequency
|
||||
*
|
||||
* @param func - Function to throttle
|
||||
* @param limit - Time limit in milliseconds
|
||||
* @returns Throttled function
|
||||
*/
|
||||
export function throttle<T extends (...args: any[]) => any>(
|
||||
func: T,
|
||||
limit: number
|
||||
): (...args: Parameters<T>) => void {
|
||||
let inThrottle: boolean;
|
||||
|
||||
return function executedFunction(...args: Parameters<T>) {
|
||||
if (!inThrottle) {
|
||||
func(...args);
|
||||
inThrottle = true;
|
||||
setTimeout(() => (inThrottle = false), limit);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a random ID
|
||||
*
|
||||
* @param length - Length of the ID (default: 8)
|
||||
* @returns Random ID string
|
||||
*/
|
||||
export function generateId(length: number = 8): string {
|
||||
return Math.random()
|
||||
.toString(36)
|
||||
.substring(2, 2 + length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if code is running in browser
|
||||
*/
|
||||
export const isBrowser = typeof window !== 'undefined';
|
||||
|
||||
/**
|
||||
* Safely access localStorage
|
||||
*/
|
||||
export const storage = {
|
||||
get: (key: string): string | null => {
|
||||
if (!isBrowser) return null;
|
||||
try {
|
||||
return localStorage.getItem(key);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
set: (key: string, value: string): void => {
|
||||
if (!isBrowser) return;
|
||||
try {
|
||||
localStorage.setItem(key, value);
|
||||
} catch {
|
||||
// Handle quota exceeded or other errors
|
||||
}
|
||||
},
|
||||
remove: (key: string): void => {
|
||||
if (!isBrowser) return;
|
||||
try {
|
||||
localStorage.removeItem(key);
|
||||
} catch {
|
||||
// Handle errors
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Copy text to clipboard
|
||||
*
|
||||
* @param text - Text to copy
|
||||
* @returns Promise<boolean> - Success status
|
||||
*/
|
||||
export async function copyToClipboard(text: string): Promise<boolean> {
|
||||
if (!isBrowser) return false;
|
||||
|
||||
try {
|
||||
if (navigator.clipboard) {
|
||||
await navigator.clipboard.writeText(text);
|
||||
return true;
|
||||
} else {
|
||||
// Fallback for older browsers
|
||||
const textarea = document.createElement('textarea');
|
||||
textarea.value = text;
|
||||
textarea.style.position = 'fixed';
|
||||
textarea.style.opacity = '0';
|
||||
document.body.appendChild(textarea);
|
||||
textarea.select();
|
||||
const success = document.execCommand('copy');
|
||||
document.body.removeChild(textarea);
|
||||
return success;
|
||||
}
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format date in relative time (e.g., "2 hours ago")
|
||||
*
|
||||
* @param date - Date to format
|
||||
* @returns Formatted relative time string
|
||||
*/
|
||||
export function formatRelativeTime(date: Date): string {
|
||||
const now = new Date();
|
||||
const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000);
|
||||
|
||||
if (diffInSeconds < 60) return 'just now';
|
||||
if (diffInSeconds < 3600) return `${Math.floor(diffInSeconds / 60)}m ago`;
|
||||
if (diffInSeconds < 86400) return `${Math.floor(diffInSeconds / 3600)}h ago`;
|
||||
if (diffInSeconds < 604800) return `${Math.floor(diffInSeconds / 86400)}d ago`;
|
||||
if (diffInSeconds < 2592000) return `${Math.floor(diffInSeconds / 604800)}w ago`;
|
||||
if (diffInSeconds < 31536000) return `${Math.floor(diffInSeconds / 2592000)}mo ago`;
|
||||
return `${Math.floor(diffInSeconds / 31536000)}y ago`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate text with ellipsis
|
||||
*
|
||||
* @param text - Text to truncate
|
||||
* @param maxLength - Maximum length
|
||||
* @returns Truncated text
|
||||
*/
|
||||
export function truncate(text: string, maxLength: number): string {
|
||||
if (text.length <= maxLength) return text;
|
||||
return text.slice(0, maxLength - 3) + '...';
|
||||
}
|
||||
|
||||
/**
|
||||
* Sleep/delay function
|
||||
*
|
||||
* @param ms - Milliseconds to sleep
|
||||
* @returns Promise that resolves after delay
|
||||
*/
|
||||
export function sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
/**
|
||||
* Clamp a number between min and max
|
||||
*
|
||||
* @param value - Value to clamp
|
||||
* @param min - Minimum value
|
||||
* @param max - Maximum value
|
||||
* @returns Clamped value
|
||||
*/
|
||||
export function clamp(value: number, min: number, max: number): number {
|
||||
return Math.min(Math.max(value, min), max);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user prefers reduced motion
|
||||
*/
|
||||
export function prefersReducedMotion(): boolean {
|
||||
if (!isBrowser) return false;
|
||||
return window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user prefers dark mode
|
||||
*/
|
||||
export function prefersDarkMode(): boolean {
|
||||
if (!isBrowser) return false;
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format number with commas
|
||||
*
|
||||
* @param num - Number to format
|
||||
* @returns Formatted number string
|
||||
*
|
||||
* @example
|
||||
* formatNumber(1000) // => "1,000"
|
||||
* formatNumber(1000000) // => "1,000,000"
|
||||
*/
|
||||
export function formatNumber(num: number): string {
|
||||
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
||||
}
|
||||
|
||||
/**
|
||||
* Abbreviate large numbers
|
||||
*
|
||||
* @param num - Number to abbreviate
|
||||
* @returns Abbreviated number string
|
||||
*
|
||||
* @example
|
||||
* abbreviateNumber(1000) // => "1K"
|
||||
* abbreviateNumber(1000000) // => "1M"
|
||||
* abbreviateNumber(1500000) // => "1.5M"
|
||||
*/
|
||||
export function abbreviateNumber(num: number): string {
|
||||
if (num < 1000) return num.toString();
|
||||
if (num < 1000000) return `${(num / 1000).toFixed(1).replace(/\.0$/, '')}K`;
|
||||
if (num < 1000000000) return `${(num / 1000000).toFixed(1).replace(/\.0$/, '')}M`;
|
||||
return `${(num / 1000000000).toFixed(1).replace(/\.0$/, '')}B`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get initials from name
|
||||
*
|
||||
* @param name - Full name
|
||||
* @param maxLength - Maximum number of initials (default: 2)
|
||||
* @returns Initials string
|
||||
*
|
||||
* @example
|
||||
* getInitials("John Doe") // => "JD"
|
||||
* getInitials("Mary Jane Watson") // => "MJ"
|
||||
*/
|
||||
export function getInitials(name: string, maxLength: number = 2): string {
|
||||
return name
|
||||
.split(' ')
|
||||
.map((n) => n[0])
|
||||
.join('')
|
||||
.toUpperCase()
|
||||
.slice(0, maxLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate email format
|
||||
*
|
||||
* @param email - Email to validate
|
||||
* @returns True if valid email format
|
||||
*/
|
||||
export function isValidEmail(email: string): boolean {
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
return emailRegex.test(email);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate URL format
|
||||
*
|
||||
* @param url - URL to validate
|
||||
* @returns True if valid URL format
|
||||
*/
|
||||
export function isValidUrl(url: string): boolean {
|
||||
try {
|
||||
new URL(url);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove HTML tags from string
|
||||
*
|
||||
* @param html - HTML string
|
||||
* @returns Plain text
|
||||
*/
|
||||
export function stripHtml(html: string): string {
|
||||
if (!isBrowser) return html;
|
||||
const tmp = document.createElement('div');
|
||||
tmp.innerHTML = html;
|
||||
return tmp.textContent || tmp.innerText || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Capitalize first letter of string
|
||||
*
|
||||
* @param str - String to capitalize
|
||||
* @returns Capitalized string
|
||||
*/
|
||||
export function capitalize(str: string): string {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert camelCase to kebab-case
|
||||
*
|
||||
* @param str - camelCase string
|
||||
* @returns kebab-case string
|
||||
*/
|
||||
export function camelToKebab(str: string): string {
|
||||
return str.replace(/([A-Z])/g, '-$1').toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert kebab-case to camelCase
|
||||
*
|
||||
* @param str - kebab-case string
|
||||
* @returns camelCase string
|
||||
*/
|
||||
export function kebabToCamel(str: string): string {
|
||||
return str.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
|
||||
}
|
||||
44
skills/frontend-design/package.json
Executable file
44
skills/frontend-design/package.json
Executable file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"name": "@z-ai/frontend-design-skill",
|
||||
"version": "2.0.0",
|
||||
"description": "Comprehensive frontend design skill for systematic and creative web development",
|
||||
"keywords": [
|
||||
"design-system",
|
||||
"design-tokens",
|
||||
"frontend",
|
||||
"ui-components",
|
||||
"react",
|
||||
"tailwind",
|
||||
"typescript",
|
||||
"accessibility"
|
||||
],
|
||||
"author": "z-ai platform team",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/z-ai/skills"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/forms": "^0.5.7",
|
||||
"@tailwindcss/typography": "^0.5.10",
|
||||
"@types/react": "^18.2.0",
|
||||
"@types/react-dom": "^18.2.0",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"clsx": "^2.1.0",
|
||||
"postcss": "^8.4.32",
|
||||
"tailwind-merge": "^2.2.0",
|
||||
"tailwindcss": "^3.4.0",
|
||||
"typescript": "^5.3.0"
|
||||
},
|
||||
"files": [
|
||||
"SKILL.md",
|
||||
"README.md",
|
||||
"LICENSE",
|
||||
"examples/**/*",
|
||||
"templates/**/*"
|
||||
]
|
||||
}
|
||||
385
skills/frontend-design/templates/globals.css
Executable file
385
skills/frontend-design/templates/globals.css
Executable file
@@ -0,0 +1,385 @@
|
||||
/**
|
||||
* Global Styles
|
||||
*
|
||||
* This file provides base styles, resets, and CSS custom properties
|
||||
* for the design system.
|
||||
*
|
||||
* Location: {project_path}/skills/frontend-design/templates/globals.css
|
||||
*
|
||||
* Import this file in your main entry point:
|
||||
* - React: import './globals.css' in index.tsx or App.tsx
|
||||
* - Next.js: import './globals.css' in pages/_app.tsx or app/layout.tsx
|
||||
*/
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
/* ============================================
|
||||
BASE LAYER - CSS Custom Properties
|
||||
============================================ */
|
||||
|
||||
@layer base {
|
||||
/* Root CSS Variables - Light Mode (Default) */
|
||||
:root {
|
||||
/* Background & Surfaces */
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 222.2 84% 4.9%;
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 222.2 84% 4.9%;
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 222.2 84% 4.9%;
|
||||
|
||||
/* Primary Brand */
|
||||
--primary: 222.2 47.4% 11.2%;
|
||||
--primary-foreground: 210 40% 98%;
|
||||
|
||||
/* Secondary */
|
||||
--secondary: 210 40% 96.1%;
|
||||
--secondary-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
/* Muted */
|
||||
--muted: 210 40% 96.1%;
|
||||
--muted-foreground: 215.4 16.3% 46.9%;
|
||||
|
||||
/* Accent */
|
||||
--accent: 210 40% 96.1%;
|
||||
--accent-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
/* Destructive/Danger */
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
|
||||
/* Borders */
|
||||
--border: 214.3 31.8% 91.4%;
|
||||
--input: 214.3 31.8% 91.4%;
|
||||
--ring: 222.2 84% 4.9%;
|
||||
|
||||
/* Misc */
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
/* Dark Mode Overrides */
|
||||
.dark {
|
||||
--background: 222.2 84% 4.9%;
|
||||
--foreground: 210 40% 98%;
|
||||
--card: 222.2 84% 4.9%;
|
||||
--card-foreground: 210 40% 98%;
|
||||
--popover: 222.2 84% 4.9%;
|
||||
--popover-foreground: 210 40% 98%;
|
||||
|
||||
--primary: 210 40% 98%;
|
||||
--primary-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--secondary: 217.2 32.6% 17.5%;
|
||||
--secondary-foreground: 210 40% 98%;
|
||||
|
||||
--muted: 217.2 32.6% 17.5%;
|
||||
--muted-foreground: 215 20.2% 65.1%;
|
||||
|
||||
--accent: 217.2 32.6% 17.5%;
|
||||
--accent-foreground: 210 40% 98%;
|
||||
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
|
||||
--border: 217.2 32.6% 17.5%;
|
||||
--input: 217.2 32.6% 17.5%;
|
||||
--ring: 212.7 26.8% 83.9%;
|
||||
}
|
||||
|
||||
/* Base Element Styles */
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
font-feature-settings: "rlig" 1, "calt" 1;
|
||||
}
|
||||
|
||||
/* Typography Defaults */
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
@apply font-semibold;
|
||||
}
|
||||
|
||||
/* Focus Visible Styles */
|
||||
*:focus-visible {
|
||||
@apply outline-none ring-2 ring-ring ring-offset-2;
|
||||
}
|
||||
|
||||
/* Smooth Scrolling */
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
/* Reduced Motion Preference */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
html {
|
||||
scroll-behavior: auto;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
scroll-behavior: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Custom Scrollbar Styles (Webkit) */
|
||||
::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
@apply bg-muted;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
@apply bg-muted-foreground/30 rounded-md;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
@apply bg-muted-foreground/50;
|
||||
}
|
||||
|
||||
/* Firefox Scrollbar */
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: hsl(var(--muted-foreground) / 0.3) hsl(var(--muted));
|
||||
}
|
||||
|
||||
/* Selection Color */
|
||||
::selection {
|
||||
@apply bg-primary/20 text-foreground;
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
COMPONENTS LAYER - Reusable Components
|
||||
============================================ */
|
||||
|
||||
@layer components {
|
||||
/* Container */
|
||||
.container {
|
||||
@apply w-full mx-auto px-4 sm:px-6 lg:px-8;
|
||||
max-width: 1536px;
|
||||
}
|
||||
|
||||
/* Button Base */
|
||||
.btn {
|
||||
@apply inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50;
|
||||
height: 2.5rem;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
height: 2rem;
|
||||
padding: 0 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.btn-lg {
|
||||
height: 3rem;
|
||||
padding: 0 1.5rem;
|
||||
}
|
||||
|
||||
/* Button Variants */
|
||||
.btn-primary {
|
||||
@apply bg-primary text-primary-foreground hover:bg-primary/90;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
@apply bg-secondary text-secondary-foreground hover:bg-secondary/80;
|
||||
}
|
||||
|
||||
.btn-outline {
|
||||
@apply border border-input bg-background hover:bg-accent hover:text-accent-foreground;
|
||||
}
|
||||
|
||||
.btn-ghost {
|
||||
@apply hover:bg-accent hover:text-accent-foreground;
|
||||
}
|
||||
|
||||
.btn-destructive {
|
||||
@apply bg-destructive text-destructive-foreground hover:bg-destructive/90;
|
||||
}
|
||||
|
||||
/* Input */
|
||||
.input {
|
||||
@apply flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50;
|
||||
}
|
||||
|
||||
/* Card */
|
||||
.card {
|
||||
@apply rounded-lg border bg-card text-card-foreground shadow-sm;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
@apply flex flex-col space-y-1.5 p-6;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
@apply text-2xl font-semibold leading-none tracking-tight;
|
||||
}
|
||||
|
||||
.card-description {
|
||||
@apply text-sm text-muted-foreground;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
@apply p-6 pt-0;
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
@apply flex items-center p-6 pt-0;
|
||||
}
|
||||
|
||||
/* Badge */
|
||||
.badge {
|
||||
@apply inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2;
|
||||
}
|
||||
|
||||
.badge-primary {
|
||||
@apply border-transparent bg-primary text-primary-foreground hover:bg-primary/80;
|
||||
}
|
||||
|
||||
.badge-secondary {
|
||||
@apply border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80;
|
||||
}
|
||||
|
||||
.badge-outline {
|
||||
@apply text-foreground;
|
||||
}
|
||||
|
||||
/* Alert */
|
||||
.alert {
|
||||
@apply relative w-full rounded-lg border p-4;
|
||||
}
|
||||
|
||||
.alert-destructive {
|
||||
@apply border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive;
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
UTILITIES LAYER - Custom Utilities
|
||||
============================================ */
|
||||
|
||||
@layer utilities {
|
||||
/* Text Balance - Prevents orphans */
|
||||
.text-balance {
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
/* Truncate with multiple lines */
|
||||
.line-clamp-2 {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.line-clamp-3 {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Hide scrollbar but keep functionality */
|
||||
.scrollbar-hide {
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.scrollbar-hide::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Animate on scroll utilities */
|
||||
.animate-on-scroll {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
transition: opacity 0.6s ease-out, transform 0.6s ease-out;
|
||||
}
|
||||
|
||||
.animate-on-scroll.in-view {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* Glassmorphism effect */
|
||||
.glass {
|
||||
@apply bg-background/80 backdrop-blur-md border border-border/50;
|
||||
}
|
||||
|
||||
/* Gradient text */
|
||||
.gradient-text {
|
||||
@apply bg-clip-text text-transparent bg-gradient-to-r from-primary to-accent;
|
||||
}
|
||||
|
||||
/* Focus ring utilities */
|
||||
.focus-ring {
|
||||
@apply focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2;
|
||||
}
|
||||
|
||||
/* Aspect ratio utilities (if not using @tailwindcss/aspect-ratio) */
|
||||
.aspect-video {
|
||||
aspect-ratio: 16 / 9;
|
||||
}
|
||||
|
||||
.aspect-square {
|
||||
aspect-ratio: 1 / 1;
|
||||
}
|
||||
|
||||
/* Safe area insets (for mobile) */
|
||||
.safe-top {
|
||||
padding-top: env(safe-area-inset-top);
|
||||
}
|
||||
|
||||
.safe-bottom {
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
|
||||
.safe-left {
|
||||
padding-left: env(safe-area-inset-left);
|
||||
}
|
||||
|
||||
.safe-right {
|
||||
padding-right: env(safe-area-inset-right);
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
PRINT STYLES
|
||||
============================================ */
|
||||
|
||||
@media print {
|
||||
/* Hide unnecessary elements when printing */
|
||||
nav, footer, .no-print {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Optimize for printing */
|
||||
body {
|
||||
@apply text-black bg-white;
|
||||
}
|
||||
|
||||
a {
|
||||
@apply text-black no-underline;
|
||||
}
|
||||
|
||||
/* Page breaks */
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
page-break-after: avoid;
|
||||
}
|
||||
|
||||
img, table, figure {
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
}
|
||||
263
skills/frontend-design/templates/tailwind.config.js
Executable file
263
skills/frontend-design/templates/tailwind.config.js
Executable file
@@ -0,0 +1,263 @@
|
||||
/**
|
||||
* Tailwind CSS Configuration
|
||||
*
|
||||
* This configuration integrates the design token system with Tailwind CSS.
|
||||
* It provides custom colors, spacing, typography, and more.
|
||||
*
|
||||
* Location: {project_path}/skills/frontend-design/templates/tailwind.config.js
|
||||
*
|
||||
* Installation:
|
||||
* 1. npm install -D tailwindcss postcss autoprefixer
|
||||
* 2. Copy this file to your project root as tailwind.config.js
|
||||
* 3. Update content paths to match your project structure
|
||||
* 4. Import globals.css in your main entry file
|
||||
*/
|
||||
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
// Specify which files Tailwind should scan for class names
|
||||
content: [
|
||||
'./src/**/*.{js,jsx,ts,tsx}',
|
||||
'./pages/**/*.{js,jsx,ts,tsx}',
|
||||
'./components/**/*.{js,jsx,ts,tsx}',
|
||||
'./app/**/*.{js,jsx,ts,tsx}',
|
||||
],
|
||||
|
||||
// Enable dark mode via class strategy
|
||||
darkMode: ['class'],
|
||||
|
||||
theme: {
|
||||
// Extend default theme (preserves Tailwind defaults)
|
||||
extend: {
|
||||
// Custom colors using CSS variables for theme support
|
||||
colors: {
|
||||
border: 'hsl(var(--border))',
|
||||
input: 'hsl(var(--input))',
|
||||
ring: 'hsl(var(--ring))',
|
||||
background: 'hsl(var(--background))',
|
||||
foreground: 'hsl(var(--foreground))',
|
||||
primary: {
|
||||
DEFAULT: 'hsl(var(--primary))',
|
||||
foreground: 'hsl(var(--primary-foreground))',
|
||||
},
|
||||
secondary: {
|
||||
DEFAULT: 'hsl(var(--secondary))',
|
||||
foreground: 'hsl(var(--secondary-foreground))',
|
||||
},
|
||||
destructive: {
|
||||
DEFAULT: 'hsl(var(--destructive))',
|
||||
foreground: 'hsl(var(--destructive-foreground))',
|
||||
},
|
||||
muted: {
|
||||
DEFAULT: 'hsl(var(--muted))',
|
||||
foreground: 'hsl(var(--muted-foreground))',
|
||||
},
|
||||
accent: {
|
||||
DEFAULT: 'hsl(var(--accent))',
|
||||
foreground: 'hsl(var(--accent-foreground))',
|
||||
},
|
||||
popover: {
|
||||
DEFAULT: 'hsl(var(--popover))',
|
||||
foreground: 'hsl(var(--popover-foreground))',
|
||||
},
|
||||
card: {
|
||||
DEFAULT: 'hsl(var(--card))',
|
||||
foreground: 'hsl(var(--card-foreground))',
|
||||
},
|
||||
},
|
||||
|
||||
// Border radius scale
|
||||
borderRadius: {
|
||||
lg: 'var(--radius)',
|
||||
md: 'calc(var(--radius) - 2px)',
|
||||
sm: 'calc(var(--radius) - 4px)',
|
||||
},
|
||||
|
||||
// Custom font families
|
||||
fontFamily: {
|
||||
sans: ['var(--font-sans)', 'system-ui', 'sans-serif'],
|
||||
serif: ['var(--font-serif)', 'Georgia', 'serif'],
|
||||
mono: ['var(--font-mono)', 'monospace'],
|
||||
},
|
||||
|
||||
// Typography scale using fluid sizing
|
||||
fontSize: {
|
||||
xs: ['clamp(0.75rem, 0.7rem + 0.15vw, 0.875rem)', { lineHeight: '1.4' }],
|
||||
sm: ['clamp(0.875rem, 0.8rem + 0.2vw, 1rem)', { lineHeight: '1.5' }],
|
||||
base: ['clamp(1rem, 0.95rem + 0.25vw, 1.125rem)', { lineHeight: '1.6' }],
|
||||
lg: ['clamp(1.125rem, 1.05rem + 0.3vw, 1.25rem)', { lineHeight: '1.5' }],
|
||||
xl: ['clamp(1.25rem, 1.15rem + 0.4vw, 1.5rem)', { lineHeight: '1.4' }],
|
||||
'2xl': ['clamp(1.5rem, 1.35rem + 0.6vw, 2rem)', { lineHeight: '1.3' }],
|
||||
'3xl': ['clamp(1.875rem, 1.65rem + 0.9vw, 2.5rem)', { lineHeight: '1.2' }],
|
||||
'4xl': ['clamp(2.25rem, 1.95rem + 1.2vw, 3.5rem)', { lineHeight: '1.1' }],
|
||||
'5xl': ['clamp(3rem, 2.5rem + 2vw, 4.5rem)', { lineHeight: '1' }],
|
||||
},
|
||||
|
||||
// Spacing scale (8px base system)
|
||||
spacing: {
|
||||
0.5: '0.125rem', // 2px
|
||||
1.5: '0.375rem', // 6px
|
||||
2.5: '0.625rem', // 10px
|
||||
3.5: '0.875rem', // 14px
|
||||
4.5: '1.125rem', // 18px
|
||||
5.5: '1.375rem', // 22px
|
||||
6.5: '1.625rem', // 26px
|
||||
7.5: '1.875rem', // 30px
|
||||
8.5: '2.125rem', // 34px
|
||||
9.5: '2.375rem', // 38px
|
||||
13: '3.25rem', // 52px
|
||||
15: '3.75rem', // 60px
|
||||
17: '4.25rem', // 68px
|
||||
18: '4.5rem', // 72px
|
||||
19: '4.75rem', // 76px
|
||||
21: '5.25rem', // 84px
|
||||
22: '5.5rem', // 88px
|
||||
23: '5.75rem', // 92px
|
||||
25: '6.25rem', // 100px
|
||||
},
|
||||
|
||||
// Custom shadows
|
||||
boxShadow: {
|
||||
sm: '0 1px 2px 0 rgb(0 0 0 / 0.05)',
|
||||
DEFAULT: '0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)',
|
||||
md: '0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)',
|
||||
lg: '0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)',
|
||||
xl: '0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)',
|
||||
'2xl': '0 25px 50px -12px rgb(0 0 0 / 0.25)',
|
||||
inner: 'inset 0 2px 4px 0 rgb(0 0 0 / 0.05)',
|
||||
},
|
||||
|
||||
// Animation durations
|
||||
transitionDuration: {
|
||||
DEFAULT: '220ms',
|
||||
fast: '150ms',
|
||||
slow: '300ms',
|
||||
},
|
||||
|
||||
// Keyframe animations
|
||||
keyframes: {
|
||||
// Fade in animation
|
||||
'fade-in': {
|
||||
'0%': { opacity: '0' },
|
||||
'100%': { opacity: '1' },
|
||||
},
|
||||
// Fade out animation
|
||||
'fade-out': {
|
||||
'0%': { opacity: '1' },
|
||||
'100%': { opacity: '0' },
|
||||
},
|
||||
// Slide in from top
|
||||
'slide-in-top': {
|
||||
'0%': { transform: 'translateY(-100%)' },
|
||||
'100%': { transform: 'translateY(0)' },
|
||||
},
|
||||
// Slide in from bottom
|
||||
'slide-in-bottom': {
|
||||
'0%': { transform: 'translateY(100%)' },
|
||||
'100%': { transform: 'translateY(0)' },
|
||||
},
|
||||
// Slide in from left
|
||||
'slide-in-left': {
|
||||
'0%': { transform: 'translateX(-100%)' },
|
||||
'100%': { transform: 'translateX(0)' },
|
||||
},
|
||||
// Slide in from right
|
||||
'slide-in-right': {
|
||||
'0%': { transform: 'translateX(100%)' },
|
||||
'100%': { transform: 'translateX(0)' },
|
||||
},
|
||||
// Scale in
|
||||
'scale-in': {
|
||||
'0%': { transform: 'scale(0.95)', opacity: '0' },
|
||||
'100%': { transform: 'scale(1)', opacity: '1' },
|
||||
},
|
||||
// Spin animation
|
||||
spin: {
|
||||
'0%': { transform: 'rotate(0deg)' },
|
||||
'100%': { transform: 'rotate(360deg)' },
|
||||
},
|
||||
// Shimmer animation (for skeletons)
|
||||
shimmer: {
|
||||
'0%': { backgroundPosition: '200% 0' },
|
||||
'100%': { backgroundPosition: '-200% 0' },
|
||||
},
|
||||
// Pulse animation
|
||||
pulse: {
|
||||
'0%, 100%': { opacity: '1' },
|
||||
'50%': { opacity: '0.5' },
|
||||
},
|
||||
// Bounce animation
|
||||
bounce: {
|
||||
'0%, 100%': { transform: 'translateY(-25%)', animationTimingFunction: 'cubic-bezier(0.8,0,1,1)' },
|
||||
'50%': { transform: 'translateY(0)', animationTimingFunction: 'cubic-bezier(0,0,0.2,1)' },
|
||||
},
|
||||
},
|
||||
|
||||
// Animation utilities
|
||||
animation: {
|
||||
'fade-in': 'fade-in 0.2s ease-out',
|
||||
'fade-out': 'fade-out 0.2s ease-out',
|
||||
'slide-in-top': 'slide-in-top 0.3s ease-out',
|
||||
'slide-in-bottom': 'slide-in-bottom 0.3s ease-out',
|
||||
'slide-in-left': 'slide-in-left 0.3s ease-out',
|
||||
'slide-in-right': 'slide-in-right 0.3s ease-out',
|
||||
'scale-in': 'scale-in 0.2s ease-out',
|
||||
spin: 'spin 0.6s linear infinite',
|
||||
shimmer: 'shimmer 1.5s ease-in-out infinite',
|
||||
pulse: 'pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite',
|
||||
bounce: 'bounce 1s infinite',
|
||||
},
|
||||
|
||||
// Custom z-index scale
|
||||
zIndex: {
|
||||
dropdown: '1000',
|
||||
sticky: '1100',
|
||||
fixed: '1200',
|
||||
'modal-backdrop': '1300',
|
||||
modal: '1400',
|
||||
popover: '1500',
|
||||
tooltip: '1600',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// Plugins
|
||||
plugins: [
|
||||
// Typography plugin for prose styling
|
||||
require('@tailwindcss/typography'),
|
||||
|
||||
// Forms plugin for better form styling
|
||||
require('@tailwindcss/forms'),
|
||||
|
||||
// Custom plugin for component utilities
|
||||
function ({ addComponents, theme }) {
|
||||
addComponents({
|
||||
// Container utility
|
||||
'.container': {
|
||||
width: '100%',
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
paddingLeft: theme('spacing.4'),
|
||||
paddingRight: theme('spacing.4'),
|
||||
'@screen sm': {
|
||||
maxWidth: '640px',
|
||||
},
|
||||
'@screen md': {
|
||||
maxWidth: '768px',
|
||||
},
|
||||
'@screen lg': {
|
||||
maxWidth: '1024px',
|
||||
paddingLeft: theme('spacing.6'),
|
||||
paddingRight: theme('spacing.6'),
|
||||
},
|
||||
'@screen xl': {
|
||||
maxWidth: '1280px',
|
||||
},
|
||||
'@screen 2xl': {
|
||||
maxWidth: '1536px',
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
],
|
||||
};
|
||||
83
skills/gift-evaluator/SKILL.md
Executable file
83
skills/gift-evaluator/SKILL.md
Executable file
@@ -0,0 +1,83 @@
|
||||
---
|
||||
name: gift-evaluator
|
||||
description: The PRIMARY tool for Spring Festival gift analysis and social interaction generation. Use this skill when users upload photos of gifts (alcohol, tea, supplements, etc.) to inquire about their value, authenticity, or how to respond socially. Integrates visual perception, market valuation, and HTML card generation.
|
||||
license: Internal Tool
|
||||
---
|
||||
|
||||
This skill transforms the assistant into an "AI Gift Appraiser" (春节礼品鉴定师). It bridges the gap between raw visual data and complex social context. It is designed to handle the full lifecycle of a user's request: identifying the object, determining its market and social value, and producing a shareable, gamified HTML artifact.
|
||||
|
||||
## Agent Thinking Strategy
|
||||
|
||||
Before and during the execution of tools, maintain a "High EQ" and "Market-Savvy" mindset. You are not just identifying objects; you are decoding social relationships.
|
||||
|
||||
1. **Visual Extraction (The Eye)**:
|
||||
* Call the vision tool to get a raw description.
|
||||
* **CRITICAL**: Read the raw description carefully. Extract specific entities: Brand names (e.g., "Moutai", "Dior"), Vintages, Packaging details (e.g., "Dusty bottle" implies old stock, "Gift box" implies formality).
|
||||
|
||||
2. **Valuation Logic (The Brain)**:
|
||||
* **Price Anchoring**: Use search tools to find the *current* market price.
|
||||
* **Social Labeling**: Classify the gift based on price and intent:
|
||||
* `luxury`: High value (> ¥1000), "Hard Currency".
|
||||
* `standard`: Festive, safe choices (¥200 - ¥1000).
|
||||
* `budget`: Practical, funny, or cheap (< ¥200).
|
||||
|
||||
3. **Creative Synthesis (The Mouth)**:
|
||||
* **Deep Critique**: Generate a "Roast" (毒舌点评) of **at least 50 words**. It must combine the visual details (e.g., dust, packaging color) with the price reality. Be spicy but insightful.
|
||||
* **Structured Strategy**: You must structure the "Thank You Notes" and "Return Gift Ideas" into JSON format for the UI to render.
|
||||
|
||||
## Tool Usage Guidelines
|
||||
### 1. The Perception Phase (Visual Analysis)
|
||||
Purpose: Utilizing VLM skills to conduct a multi-dimensional visual decomposition of the uploaded product image. This process automatically identifies and extracts structured data including Brand Recognition, Product Style, Packaging Design, and Aesthetic Category.
|
||||
|
||||
**Output Analysis**:
|
||||
|
||||
* The tool returns a raw string content. Read it to extract keywords for the next step.
|
||||
|
||||
### 2. The Valuation Phase (Search)
|
||||
|
||||
**Purpose**: Validate the product's worth.
|
||||
**Command**:search "EXTRACTED_KEYWORDS + price + review"
|
||||
|
||||
|
||||
### 3. The Content Structuring Phase (Reasoning)
|
||||
|
||||
**Purpose**: Prepare the data for the HTML generator. **Do not call a tool here, just think and format strings.**
|
||||
|
||||
1. **Construct `thank_you_json**`: Create 3 distinct styles of private messages.
|
||||
* *Format*: `[{"style": "Style Name", "content": "Message..."}]`
|
||||
* *Requirement*:
|
||||
* Style 1: "Decent/Formal" (for elders/bosses).
|
||||
* Style 2: "Friendly/Warm" (for peers/relatives).
|
||||
* Style 3: "Humorous/Close" (for best friends).
|
||||
|
||||
|
||||
2. **Construct `return_gift_json**`: Analyze 4 potential giver personas.
|
||||
* *Format*: `[{"target": "If giver is...", "item": "Suggest...", "reason": "Why..."}]`
|
||||
* *Requirement*: Suggestions must include Age/Gender/Relation analysis (e.g., "If giver is an elder male", "If giver is a peer female").
|
||||
* *Value Logic*: Adhere to the principle of Value Reciprocity. The return gift's value should primarily match the received gift's value, while adjusting slightly based on the giver's status (e.g., seniority or intimacy).
|
||||
|
||||
|
||||
### 4. The Creation Phase (Render)
|
||||
|
||||
**Purpose**: Package the analysis into a modern, interactive HTML card.
|
||||
**HTML Generation**:
|
||||
* *Constraint*: The `image_url` parameter in the Python command MUST be the original absolute path.`output_path` must be the full path.
|
||||
* *Command*:
|
||||
```bash
|
||||
python3 html_tools.py generate_gift_card \
|
||||
--product_name "EXTRACTED_NAME" \
|
||||
--price "ESTIMATED_PRICE" \
|
||||
--evaluation "YOUR_LONG_AND_SPICY_CRITIQUE" \
|
||||
--thank_you_json '[{"style":"...","content":"..."}]' \
|
||||
--return_gift_json '[{"target":"...","item":"...","reason":"..."}]' \
|
||||
--vibe_code "luxury|standard|budget" \
|
||||
--image_url "IMAGE_FILE_PATH" \
|
||||
--output_path "TARGET_FILE_PATH"
|
||||
```
|
||||
|
||||
## Operational Rules
|
||||
|
||||
1. **JSON Formatting**: The `thank_you_json` and `return_gift_json` arguments MUST be valid JSON strings using double quotes. Do not wrap them in code blocks inside the command.
|
||||
2. **Critique Depth**: The `evaluation` text must be rich. Don't just say "It's expensive." Say "This 2018 vintage shows your uncle raided his personal cellar; the label wear proves it's real."
|
||||
3. **Vibe Consistency**: Ensure `vibe_code` matches the `price` assessment.
|
||||
4. **Final Output**: Always present the path to the generated HTML file.
|
||||
268
skills/gift-evaluator/html_tools.py
Executable file
268
skills/gift-evaluator/html_tools.py
Executable file
@@ -0,0 +1,268 @@
|
||||
import os
|
||||
import argparse
|
||||
import json
|
||||
import html
|
||||
import base64
|
||||
import mimetypes
|
||||
import urllib.request
|
||||
|
||||
def generate_gift_card(product_name, price, evaluation, thank_you_json, return_gift_json, vibe_code, image_url, output_path="gift_card_result.html"):
|
||||
"""
|
||||
生成现代风格的交互式礼品鉴定卡片。
|
||||
"""
|
||||
|
||||
# --- 图片转 Base64 逻辑 (保持上一步功能) ---
|
||||
final_image_src = image_url
|
||||
try:
|
||||
image_data = None
|
||||
mime_type = None
|
||||
if image_url.startswith(('http://', 'https://')):
|
||||
req = urllib.request.Request(image_url, headers={'User-Agent': 'Mozilla/5.0'})
|
||||
with urllib.request.urlopen(req, timeout=10) as response:
|
||||
image_data = response.read()
|
||||
mime_type = response.headers.get_content_type()
|
||||
else:
|
||||
if os.path.exists(image_url):
|
||||
mime_type, _ = mimetypes.guess_type(image_url)
|
||||
with open(image_url, "rb") as f:
|
||||
image_data = f.read()
|
||||
|
||||
if image_data:
|
||||
if not mime_type: mime_type = "image/jpeg"
|
||||
b64_str = base64.b64encode(image_data).decode('utf-8')
|
||||
final_image_src = f"data:{mime_type};base64,{b64_str}"
|
||||
|
||||
except Exception as e:
|
||||
print(f"⚠️ 图片转换 Base64 失败,使用原链接。错误: {e}")
|
||||
|
||||
# --- 1. 数据解析 ---
|
||||
try:
|
||||
thank_you_data = json.loads(thank_you_json)
|
||||
except:
|
||||
thank_you_data = [{"style": "通用版", "content": thank_you_json}]
|
||||
|
||||
try:
|
||||
return_gift_data = json.loads(return_gift_json)
|
||||
except:
|
||||
return_gift_data = [{"target": "通用建议", "item": return_gift_json, "reason": "万能回礼"}]
|
||||
|
||||
# --- 2. 风格配置 ---
|
||||
styles = {
|
||||
"luxury": {
|
||||
"page_bg": "bg-neutral-900",
|
||||
"card_bg": "bg-neutral-900/80 backdrop-blur-xl border border-white/10",
|
||||
"text_main": "text-white", "text_sub": "text-neutral-400",
|
||||
"accent": "text-amber-400", "tag_bg": "bg-amber-400/20 text-amber-400",
|
||||
"btn_hover": "hover:bg-amber-400 hover:text-black",
|
||||
"img_bg": "bg-neutral-800" # 图片衬底色
|
||||
},
|
||||
"standard": {
|
||||
"page_bg": "bg-stone-200",
|
||||
"card_bg": "bg-white/95 backdrop-blur-xl border border-stone-200",
|
||||
"text_main": "text-stone-800", "text_sub": "text-stone-500",
|
||||
"accent": "text-red-600", "tag_bg": "bg-red-50 text-red-600",
|
||||
"btn_hover": "hover:bg-red-600 hover:text-white",
|
||||
"img_bg": "bg-stone-100"
|
||||
},
|
||||
"budget": {
|
||||
"page_bg": "bg-yellow-50",
|
||||
"card_bg": "bg-white border-4 border-black shadow-[8px_8px_0px_0px_rgba(0,0,0,1)]",
|
||||
"text_main": "text-black", "text_sub": "text-gray-600",
|
||||
"accent": "text-blue-600", "tag_bg": "bg-black text-white",
|
||||
"btn_hover": "hover:bg-blue-600 hover:text-white",
|
||||
"img_bg": "bg-gray-200"
|
||||
}
|
||||
}
|
||||
st = styles.get(vibe_code, styles["standard"])
|
||||
if "img_bg" not in st: st["img_bg"] = "bg-black/5" # 兼容兜底
|
||||
|
||||
# --- 3. 辅助逻辑 ---
|
||||
is_dark_mode = "text-white" in st['text_main']
|
||||
bubble_bg = "bg-white/10 border-white/10" if is_dark_mode else "bg-black/5 border-black/5"
|
||||
bubble_hover = "hover:bg-white/20" if is_dark_mode else "hover:bg-black/10"
|
||||
divider_color = "border-white/20" if is_dark_mode else "border-black/10"
|
||||
|
||||
# --- 4. HTML 构建 ---
|
||||
thank_you_html = ""
|
||||
for item in thank_you_data:
|
||||
thank_you_html += f"""
|
||||
<div class="group relative p-4 rounded-xl {bubble_bg} border {bubble_hover} transition-all cursor-pointer mb-3" onclick="copyText(this, '{html.escape(item['content'], quote=True)}')">
|
||||
<div class="flex justify-between items-center mb-2">
|
||||
<span class="text-xs font-bold {st['accent']} border border-current px-2 py-0.5 rounded-full">{item['style']}</span>
|
||||
<span class="text-[10px] opacity-60 group-hover:opacity-100 transition-opacity {st['text_sub']} flex items-center gap-1">
|
||||
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg>
|
||||
点击复制
|
||||
</span>
|
||||
</div>
|
||||
<p class="text-sm {st['text_main']} leading-relaxed opacity-95 font-medium">{item['content']}</p>
|
||||
<div class="copy-feedback absolute inset-0 bg-{st['accent'].split('-')[1]}-500 text-white flex items-center justify-center rounded-xl opacity-0 pointer-events-none transition-opacity duration-200 font-bold z-10">
|
||||
<span>✓ 已复制</span>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
|
||||
return_gift_html = ""
|
||||
for item in return_gift_data:
|
||||
return_gift_html += f"""
|
||||
<div class="p-4 rounded-xl {bubble_bg} border flex flex-col justify-between h-full hover:scale-[1.02] transition-transform duration-300">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<div class="w-1.5 h-1.5 rounded-full bg-current {st['accent']}"></div>
|
||||
<div class="text-xs font-bold uppercase tracking-wider {st['text_sub']}">{item['target']}</div>
|
||||
</div>
|
||||
<div class="font-bold {st['text_main']} text-lg mb-2">{item['item']}</div>
|
||||
<div class="text-xs {st['text_sub']} opacity-80 leading-snug bg-black/5 dark:bg-white/5 p-2 rounded">{item['reason']}</div>
|
||||
</div>
|
||||
"""
|
||||
|
||||
html_content = f"""
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>礼品鉴定报告</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800&family=Noto+Serif+SC:wght@700;900&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {{ font-family: 'Inter', sans-serif; }}
|
||||
.serif {{ font-family: 'Noto Serif SC', serif; }}
|
||||
/* 通用滚动条样式 */
|
||||
.custom-scroll::-webkit-scrollbar {{ width: 4px; }}
|
||||
.custom-scroll::-webkit-scrollbar-thumb {{ background-color: rgba(150,150,150,0.3); border-radius: 4px; }}
|
||||
.custom-scroll:hover::-webkit-scrollbar-thumb {{ background-color: rgba(150,150,150,0.6); }}
|
||||
</style>
|
||||
</head>
|
||||
<body class="{st['page_bg']} min-h-screen flex items-center justify-center p-2 md:p-8 selection:bg-red-200 selection:text-red-900">
|
||||
<div class="w-full max-w-6xl {st['card_bg']} rounded-[2.5rem] shadow-2xl overflow-hidden relative flex flex-col md:flex-row md:h-[750px] transition-all duration-500">
|
||||
|
||||
<div class="w-full md:w-[45%] flex flex-col relative shrink-0 border-b md:border-b-0 md:border-r {divider_color}">
|
||||
|
||||
<div class="relative h-72 md:h-[55%] group overflow-hidden {st['img_bg']} flex items-center justify-center p-6">
|
||||
<img src="{final_image_src}" class="w-full h-full object-contain relative z-10 drop-shadow-xl transition-transform duration-700 group-hover:scale-105">
|
||||
|
||||
<div class="absolute inset-x-0 bottom-0 h-32 bg-gradient-to-t from-black/80 to-transparent z-20 pointer-events-none"></div>
|
||||
|
||||
<div class="absolute bottom-6 left-6 right-6 z-30">
|
||||
<div class="inline-block px-3 py-1 rounded-lg text-[10px] font-bold uppercase tracking-widest mb-2 {st['tag_bg']} backdrop-blur-md shadow-lg">
|
||||
AI Gift Analysis
|
||||
</div>
|
||||
<h1 class="text-3xl md:text-4xl font-black text-white leading-tight serif mb-1 drop-shadow-md truncate">{product_name}</h1>
|
||||
<div class="flex items-baseline gap-2 text-white/90">
|
||||
<span class="text-sm font-light opacity-80">当前估值</span>
|
||||
<span class="text-3xl font-bold tracking-tight">{price}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 p-6 md:p-8 flex flex-col min-h-0 bg-inherit relative">
|
||||
<div class="absolute top-0 left-8 -mt-5 text-6xl opacity-20 {st['text_main']} font-serif select-none">“</div>
|
||||
|
||||
<h3 class="text-xs font-bold uppercase tracking-widest {st['text_sub']} mb-3 flex items-center gap-2 shrink-0">
|
||||
<span class="w-8 h-[1px] bg-current opacity-50"></span>
|
||||
专家鉴定评价
|
||||
</h3>
|
||||
|
||||
<div class="{st['text_main']} text-base md:text-lg leading-relaxed italic font-medium relative z-10 overflow-y-auto custom-scroll flex-1 pr-2">
|
||||
{evaluation}
|
||||
</div>
|
||||
|
||||
<div class="mt-4 pt-4 border-t {divider_color} flex items-center gap-3 shrink-0">
|
||||
<div class="w-8 h-8 rounded-full {st['tag_bg']} flex items-center justify-center font-bold text-xs">AI</div>
|
||||
<div class="flex flex-col">
|
||||
<span class="text-xs font-bold {st['text_main']}">首席鉴定官</span>
|
||||
<span class="text-[10px] {st['text_sub']}">Verified Analysis</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="w-full md:w-[55%] overflow-y-auto custom-scroll p-6 md:p-10 flex flex-col gap-8 bg-inherit">
|
||||
<div>
|
||||
<div class="flex items-center gap-3 mb-5 border-b {divider_color} pb-3">
|
||||
<div class="p-2 rounded-lg {st['tag_bg']}">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z"></path></svg>
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="text-xl md:text-2xl font-bold {st['text_main']}">私信回复话术</h2>
|
||||
<p class="text-xs {st['text_sub']}">高情商回复,点击卡片即可复制</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
{thank_you_html}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="flex items-center gap-3 mb-5 mt-2 border-b {divider_color} pb-3">
|
||||
<div class="p-2 rounded-lg {st['tag_bg']}">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v13m0-13V6a2 2 0 112 2h-2zm0 0V5.5A2.5 2.5 0 109.5 8H12zm-7 4h14M5 12a2 2 0 110-4h14a2 2 0 110 4M5 12v7a2 2 0 002 2h10a2 2 0 002-2v-7"></path></svg>
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="text-xl md:text-2xl font-bold {st['text_main']}">推荐回礼策略</h2>
|
||||
<p class="text-xs {st['text_sub']}">基于价格区间的最优解</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||
{return_gift_html}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-auto pt-8 text-center opacity-40">
|
||||
<p class="text-[10px] {st['text_sub']}">Designed by AI Gift Agent • 春节特别版</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function copyText(element, text) {{
|
||||
navigator.clipboard.writeText(text).then(() => {{
|
||||
const feedback = element.querySelector('.copy-feedback');
|
||||
feedback.classList.remove('opacity-0');
|
||||
feedback.classList.remove('pointer-events-none');
|
||||
setTimeout(() => {{
|
||||
feedback.classList.add('opacity-0');
|
||||
feedback.classList.add('pointer-events-none');
|
||||
}}, 1500);
|
||||
}});
|
||||
}}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
try:
|
||||
directory = os.path.dirname(output_path)
|
||||
if directory:
|
||||
os.makedirs(directory, exist_ok=True)
|
||||
with open(output_path, "w", encoding="utf-8") as f:
|
||||
f.write(html_content)
|
||||
return os.path.abspath(output_path)
|
||||
except Exception as e:
|
||||
return f"Error saving HTML file: {str(e)}"
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Generate Gift Card HTML")
|
||||
parser.add_argument("action", nargs="?", help="Action command")
|
||||
parser.add_argument("--product_name", required=True)
|
||||
parser.add_argument("--price", required=True)
|
||||
parser.add_argument("--evaluation", required=True)
|
||||
parser.add_argument("--thank_you_json", required=True)
|
||||
parser.add_argument("--return_gift_json", required=True)
|
||||
parser.add_argument("--vibe_code", required=True)
|
||||
parser.add_argument("--image_url", required=True)
|
||||
parser.add_argument("--output_path", required=True)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
result_path = generate_gift_card(
|
||||
product_name=args.product_name,
|
||||
price=args.price,
|
||||
evaluation=args.evaluation,
|
||||
thank_you_json=args.thank_you_json,
|
||||
return_gift_json=args.return_gift_json,
|
||||
vibe_code=args.vibe_code,
|
||||
image_url=args.image_url,
|
||||
output_path=args.output_path
|
||||
)
|
||||
|
||||
print(f"HTML Card generated successfully: {result_path}")
|
||||
21
skills/image-generation/LICENSE.txt
Executable file
21
skills/image-generation/LICENSE.txt
Executable file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 z-ai-web-dev-sdk Skills
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
583
skills/image-generation/SKILL.md
Executable file
583
skills/image-generation/SKILL.md
Executable file
@@ -0,0 +1,583 @@
|
||||
---
|
||||
name: image-generation
|
||||
description: Implement AI image generation capabilities using the z-ai-web-dev-sdk. Use this skill when the user needs to create images from text descriptions, generate visual content, create artwork, design assets, or build applications with AI-powered image creation. Supports multiple image sizes and returns base64 encoded images. Also includes CLI tool for quick image generation.
|
||||
license: MIT
|
||||
---
|
||||
|
||||
# Image Generation Skill
|
||||
|
||||
This skill guides the implementation of image generation functionality using the z-ai-web-dev-sdk package and CLI tool, enabling creation of high-quality images from text descriptions.
|
||||
|
||||
## Skills Path
|
||||
|
||||
**Skill Location**: `{project_path}/skills/image-generation`
|
||||
|
||||
this skill is located at above path in your project.
|
||||
|
||||
**Reference Scripts**: Example test scripts are available in the `{Skill Location}/scripts/` directory for quick testing and reference. See `{Skill Location}/scripts/image-generation.ts` for a working example.
|
||||
|
||||
## Overview
|
||||
|
||||
Image Generation allows you to build applications that create visual content from text prompts using AI models, enabling creative workflows, design automation, and visual content production.
|
||||
|
||||
**IMPORTANT**: z-ai-web-dev-sdk MUST be used in backend code only. Never use it in client-side code.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
The z-ai-web-dev-sdk package is already installed. Import it as shown in the examples below.
|
||||
|
||||
## Basic Image Generation
|
||||
|
||||
### Simple Image Creation
|
||||
|
||||
```javascript
|
||||
import ZAI from 'z-ai-web-dev-sdk';
|
||||
import fs from 'fs';
|
||||
|
||||
async function generateImage(prompt, outputPath) {
|
||||
const zai = await ZAI.create();
|
||||
|
||||
const response = await zai.images.generations.create({
|
||||
prompt: prompt,
|
||||
size: '1024x1024'
|
||||
});
|
||||
|
||||
const imageBase64 = response.data[0].base64;
|
||||
|
||||
// Save image
|
||||
const buffer = Buffer.from(imageBase64, 'base64');
|
||||
fs.writeFileSync(outputPath, buffer);
|
||||
|
||||
console.log(`Image saved to ${outputPath}`);
|
||||
return outputPath;
|
||||
}
|
||||
|
||||
// Usage
|
||||
await generateImage(
|
||||
'A cute cat playing in the garden',
|
||||
'./cat_image.png'
|
||||
);
|
||||
```
|
||||
|
||||
### Multiple Image Sizes
|
||||
|
||||
```javascript
|
||||
import ZAI from 'z-ai-web-dev-sdk';
|
||||
import fs from 'fs';
|
||||
|
||||
// Supported sizes
|
||||
const SUPPORTED_SIZES = [
|
||||
'1024x1024', // Square
|
||||
'768x1344', // Portrait
|
||||
'864x1152', // Portrait
|
||||
'1344x768', // Landscape
|
||||
'1152x864', // Landscape
|
||||
'1440x720', // Wide landscape
|
||||
'720x1440' // Tall portrait
|
||||
];
|
||||
|
||||
async function generateImageWithSize(prompt, size, outputPath) {
|
||||
if (!SUPPORTED_SIZES.includes(size)) {
|
||||
throw new Error(`Unsupported size: ${size}. Use one of: ${SUPPORTED_SIZES.join(', ')}`);
|
||||
}
|
||||
|
||||
const zai = await ZAI.create();
|
||||
|
||||
const response = await zai.images.generations.create({
|
||||
prompt: prompt,
|
||||
size: size
|
||||
});
|
||||
|
||||
const imageBase64 = response.data[0].base64;
|
||||
const buffer = Buffer.from(imageBase64, 'base64');
|
||||
fs.writeFileSync(outputPath, buffer);
|
||||
|
||||
return {
|
||||
path: outputPath,
|
||||
size: size,
|
||||
fileSize: buffer.length
|
||||
};
|
||||
}
|
||||
|
||||
// Usage - Different sizes
|
||||
await generateImageWithSize(
|
||||
'A beautiful landscape',
|
||||
'1344x768',
|
||||
'./landscape.png'
|
||||
);
|
||||
|
||||
await generateImageWithSize(
|
||||
'A portrait of a person',
|
||||
'768x1344',
|
||||
'./portrait.png'
|
||||
);
|
||||
```
|
||||
|
||||
## CLI Tool Usage
|
||||
|
||||
The z-ai CLI tool provides a convenient way to generate images directly from the command line.
|
||||
|
||||
### Basic CLI Usage
|
||||
|
||||
```bash
|
||||
# Generate image with full options
|
||||
z-ai image --prompt "A beautiful landscape" --output "./image.png"
|
||||
|
||||
# Short form
|
||||
z-ai image -p "A cute cat" -o "./cat.png"
|
||||
|
||||
# Specify size
|
||||
z-ai image -p "A sunset" -o "./sunset.png" -s 1344x768
|
||||
|
||||
# Portrait orientation
|
||||
z-ai image -p "A portrait" -o "./portrait.png" -s 768x1344
|
||||
```
|
||||
|
||||
### CLI Use Cases
|
||||
|
||||
```bash
|
||||
# Website hero image
|
||||
z-ai image -p "Modern tech office with diverse team collaborating" -o "./hero.png" -s 1440x720
|
||||
|
||||
# Product image
|
||||
z-ai image -p "Sleek smartphone on minimalist desk, professional product photography" -o "./product.png" -s 1024x1024
|
||||
|
||||
# Blog post illustration
|
||||
z-ai image -p "Abstract visualization of data flowing through networks" -o "./blog_header.png" -s 1344x768
|
||||
|
||||
# Social media content
|
||||
z-ai image -p "Vibrant illustration of community connection" -o "./social.png" -s 1024x1024
|
||||
|
||||
# Website favicon/logo
|
||||
z-ai image -p "Simple geometric logo with blue gradient, minimal design" -o "./logo.png" -s 1024x1024
|
||||
|
||||
# Background pattern
|
||||
z-ai image -p "Subtle geometric pattern, pastel colors, website background" -o "./bg_pattern.png" -s 1440x720
|
||||
```
|
||||
|
||||
## Advanced Use Cases
|
||||
|
||||
### Batch Image Generation
|
||||
|
||||
```javascript
|
||||
import ZAI from 'z-ai-web-dev-sdk';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
async function generateImageBatch(prompts, outputDir, size = '1024x1024') {
|
||||
const zai = await ZAI.create();
|
||||
|
||||
// Ensure output directory exists
|
||||
if (!fs.existsSync(outputDir)) {
|
||||
fs.mkdirSync(outputDir, { recursive: true });
|
||||
}
|
||||
|
||||
const results = [];
|
||||
|
||||
for (let i = 0; i < prompts.length; i++) {
|
||||
try {
|
||||
const prompt = prompts[i];
|
||||
const filename = `image_${i + 1}.png`;
|
||||
const outputPath = path.join(outputDir, filename);
|
||||
|
||||
const response = await zai.images.generations.create({
|
||||
prompt: prompt,
|
||||
size: size
|
||||
});
|
||||
|
||||
const imageBase64 = response.data[0].base64;
|
||||
const buffer = Buffer.from(imageBase64, 'base64');
|
||||
fs.writeFileSync(outputPath, buffer);
|
||||
|
||||
results.push({
|
||||
success: true,
|
||||
prompt: prompt,
|
||||
path: outputPath,
|
||||
size: buffer.length
|
||||
});
|
||||
|
||||
console.log(`✓ Generated: ${filename}`);
|
||||
} catch (error) {
|
||||
results.push({
|
||||
success: false,
|
||||
prompt: prompts[i],
|
||||
error: error.message
|
||||
});
|
||||
|
||||
console.error(`✗ Failed: ${prompts[i]} - ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
// Usage
|
||||
const prompts = [
|
||||
'A serene mountain landscape at sunset',
|
||||
'A futuristic city with flying cars',
|
||||
'An underwater coral reef teeming with life'
|
||||
];
|
||||
|
||||
const results = await generateImageBatch(prompts, './generated-images');
|
||||
console.log(`Generated ${results.filter(r => r.success).length} images`);
|
||||
```
|
||||
|
||||
### Image Generation Service
|
||||
|
||||
```javascript
|
||||
import ZAI from 'z-ai-web-dev-sdk';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import crypto from 'crypto';
|
||||
|
||||
class ImageGenerationService {
|
||||
constructor(outputDir = './generated-images') {
|
||||
this.outputDir = outputDir;
|
||||
this.zai = null;
|
||||
this.cache = new Map();
|
||||
}
|
||||
|
||||
async initialize() {
|
||||
this.zai = await ZAI.create();
|
||||
|
||||
if (!fs.existsSync(this.outputDir)) {
|
||||
fs.mkdirSync(this.outputDir, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
generateCacheKey(prompt, size) {
|
||||
return crypto
|
||||
.createHash('md5')
|
||||
.update(`${prompt}-${size}`)
|
||||
.digest('hex');
|
||||
}
|
||||
|
||||
async generate(prompt, options = {}) {
|
||||
const {
|
||||
size = '1024x1024',
|
||||
useCache = true,
|
||||
filename = null
|
||||
} = options;
|
||||
|
||||
// Check cache
|
||||
const cacheKey = this.generateCacheKey(prompt, size);
|
||||
|
||||
if (useCache && this.cache.has(cacheKey)) {
|
||||
const cachedPath = this.cache.get(cacheKey);
|
||||
if (fs.existsSync(cachedPath)) {
|
||||
return {
|
||||
path: cachedPath,
|
||||
cached: true,
|
||||
prompt: prompt,
|
||||
size: size
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Generate new image
|
||||
const response = await this.zai.images.generations.create({
|
||||
prompt: prompt,
|
||||
size: size
|
||||
});
|
||||
|
||||
const imageBase64 = response.data[0].base64;
|
||||
const buffer = Buffer.from(imageBase64, 'base64');
|
||||
|
||||
// Determine output path
|
||||
const outputFilename = filename || `${cacheKey}.png`;
|
||||
const outputPath = path.join(this.outputDir, outputFilename);
|
||||
|
||||
fs.writeFileSync(outputPath, buffer);
|
||||
|
||||
// Cache result
|
||||
if (useCache) {
|
||||
this.cache.set(cacheKey, outputPath);
|
||||
}
|
||||
|
||||
return {
|
||||
path: outputPath,
|
||||
cached: false,
|
||||
prompt: prompt,
|
||||
size: size,
|
||||
fileSize: buffer.length
|
||||
};
|
||||
}
|
||||
|
||||
clearCache() {
|
||||
this.cache.clear();
|
||||
}
|
||||
|
||||
getCacheSize() {
|
||||
return this.cache.size;
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
const service = new ImageGenerationService();
|
||||
await service.initialize();
|
||||
|
||||
const result = await service.generate(
|
||||
'A modern office space',
|
||||
{ size: '1440x720' }
|
||||
);
|
||||
|
||||
console.log('Generated:', result.path);
|
||||
```
|
||||
|
||||
### Website Asset Generator
|
||||
|
||||
```bash
|
||||
# Using CLI for quick website asset generation
|
||||
z-ai image -p "Modern tech hero banner, blue gradient" -o "./assets/hero.png" -s 1440x720
|
||||
z-ai image -p "Team collaboration illustration" -o "./assets/team.png" -s 1344x768
|
||||
z-ai image -p "Simple geometric logo" -o "./assets/logo.png" -s 1024x1024
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Effective Prompt Engineering
|
||||
|
||||
```javascript
|
||||
function buildEffectivePrompt(subject, style, details = []) {
|
||||
const components = [
|
||||
subject,
|
||||
style,
|
||||
...details,
|
||||
'high quality',
|
||||
'detailed'
|
||||
];
|
||||
|
||||
return components.filter(Boolean).join(', ');
|
||||
}
|
||||
|
||||
// Usage
|
||||
const prompt = buildEffectivePrompt(
|
||||
'mountain landscape',
|
||||
'oil painting style',
|
||||
['sunset lighting', 'dramatic clouds', 'reflection in lake']
|
||||
);
|
||||
|
||||
// Result: "mountain landscape, oil painting style, sunset lighting, dramatic clouds, reflection in lake, high quality, detailed"
|
||||
```
|
||||
|
||||
### 2. Size Selection Helper
|
||||
|
||||
```javascript
|
||||
function selectOptimalSize(purpose) {
|
||||
const sizeMap = {
|
||||
'hero-banner': '1440x720',
|
||||
'blog-header': '1344x768',
|
||||
'social-square': '1024x1024',
|
||||
'portrait': '768x1344',
|
||||
'product': '1024x1024',
|
||||
'landscape': '1344x768',
|
||||
'mobile-banner': '720x1440',
|
||||
'thumbnail': '1024x1024'
|
||||
};
|
||||
|
||||
return sizeMap[purpose] || '1024x1024';
|
||||
}
|
||||
|
||||
// Usage
|
||||
const size = selectOptimalSize('hero-banner');
|
||||
await generateImage('website hero image', size, './hero.png');
|
||||
```
|
||||
|
||||
### 3. Error Handling
|
||||
|
||||
```javascript
|
||||
import ZAI from 'z-ai-web-dev-sdk';
|
||||
import fs from 'fs';
|
||||
|
||||
async function safeGenerateImage(prompt, size, outputPath, retries = 3) {
|
||||
let lastError;
|
||||
|
||||
for (let attempt = 1; attempt <= retries; attempt++) {
|
||||
try {
|
||||
const zai = await ZAI.create();
|
||||
|
||||
const response = await zai.images.generations.create({
|
||||
prompt: prompt,
|
||||
size: size
|
||||
});
|
||||
|
||||
if (!response.data || !response.data[0] || !response.data[0].base64) {
|
||||
throw new Error('Invalid response from image generation API');
|
||||
}
|
||||
|
||||
const imageBase64 = response.data[0].base64;
|
||||
const buffer = Buffer.from(imageBase64, 'base64');
|
||||
fs.writeFileSync(outputPath, buffer);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
path: outputPath,
|
||||
attempts: attempt
|
||||
};
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
console.error(`Attempt ${attempt} failed:`, error.message);
|
||||
|
||||
if (attempt < retries) {
|
||||
// Wait before retry (exponential backoff)
|
||||
await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: lastError.message,
|
||||
attempts: retries
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Common Use Cases
|
||||
|
||||
1. **Website Design**: Generate hero images, backgrounds, and visual assets
|
||||
2. **Marketing Materials**: Create social media graphics and promotional images
|
||||
3. **Product Visualization**: Generate product mockups and variations
|
||||
4. **Content Creation**: Produce blog post illustrations and thumbnails
|
||||
5. **Brand Assets**: Create logos, icons, and brand imagery
|
||||
6. **UI/UX Design**: Generate interface elements and illustrations
|
||||
7. **Game Development**: Create concept art and game assets
|
||||
8. **E-commerce**: Generate product images and lifestyle shots
|
||||
|
||||
## Integration Examples
|
||||
|
||||
### Express.js API Endpoint
|
||||
|
||||
```javascript
|
||||
import express from 'express';
|
||||
import ZAI from 'z-ai-web-dev-sdk';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
app.use('/images', express.static('generated-images'));
|
||||
|
||||
let zaiInstance;
|
||||
const outputDir = './generated-images';
|
||||
|
||||
async function initZAI() {
|
||||
zaiInstance = await ZAI.create();
|
||||
if (!fs.existsSync(outputDir)) {
|
||||
fs.mkdirSync(outputDir, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
app.post('/api/generate-image', async (req, res) => {
|
||||
try {
|
||||
const { prompt, size = '1024x1024' } = req.body;
|
||||
|
||||
if (!prompt) {
|
||||
return res.status(400).json({ error: 'Prompt is required' });
|
||||
}
|
||||
|
||||
const response = await zaiInstance.images.generations.create({
|
||||
prompt: prompt,
|
||||
size: size
|
||||
});
|
||||
|
||||
const imageBase64 = response.data[0].base64;
|
||||
const buffer = Buffer.from(imageBase64, 'base64');
|
||||
|
||||
const filename = `img_${Date.now()}.png`;
|
||||
const filepath = path.join(outputDir, filename);
|
||||
fs.writeFileSync(filepath, buffer);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
imageUrl: `/images/${filename}`,
|
||||
prompt: prompt,
|
||||
size: size
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
initZAI().then(() => {
|
||||
app.listen(3000, () => {
|
||||
console.log('Image generation API running on port 3000');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## CLI Integration in Scripts
|
||||
|
||||
### Shell Script Example
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
# Generate website assets using CLI
|
||||
echo "Generating website assets..."
|
||||
|
||||
z-ai image -p "Modern tech hero banner, blue gradient" -o "./assets/hero.png" -s 1440x720
|
||||
z-ai image -p "Team collaboration illustration" -o "./assets/team.png" -s 1344x768
|
||||
z-ai image -p "Simple geometric logo" -o "./assets/logo.png" -s 1024x1024
|
||||
|
||||
echo "Assets generated successfully!"
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Issue**: "SDK must be used in backend"
|
||||
- **Solution**: Ensure z-ai-web-dev-sdk is only used in server-side code
|
||||
|
||||
**Issue**: Invalid size parameter
|
||||
- **Solution**: Use only supported sizes: 1024x1024, 768x1344, 864x1152, 1344x768, 1152x864, 1440x720, 720x1440
|
||||
|
||||
**Issue**: Generated image doesn't match prompt
|
||||
- **Solution**: Make prompts more specific and descriptive. Include style, details, and quality terms
|
||||
|
||||
**Issue**: CLI command not found
|
||||
- **Solution**: Ensure z-ai CLI is properly installed and in PATH
|
||||
|
||||
**Issue**: Image file is corrupted
|
||||
- **Solution**: Verify base64 decoding and file writing are correct
|
||||
|
||||
## Prompt Engineering Tips
|
||||
|
||||
### Good Prompts
|
||||
- ✓ "Professional product photography of wireless headphones, white background, studio lighting, high quality"
|
||||
- ✓ "Mountain landscape at golden hour, oil painting style, dramatic clouds, detailed"
|
||||
- ✓ "Modern minimalist logo for tech company, blue and white, geometric shapes"
|
||||
|
||||
### Poor Prompts
|
||||
- ✗ "headphones"
|
||||
- ✗ "picture of mountains"
|
||||
- ✗ "logo"
|
||||
|
||||
### Prompt Components
|
||||
1. **Subject**: What you want to see
|
||||
2. **Style**: Art style, photography style, etc.
|
||||
3. **Details**: Specific elements, colors, mood
|
||||
4. **Quality**: "high quality", "detailed", "professional"
|
||||
|
||||
## Supported Image Sizes
|
||||
|
||||
- `1024x1024` - Square
|
||||
- `768x1344` - Portrait
|
||||
- `864x1152` - Portrait
|
||||
- `1344x768` - Landscape
|
||||
- `1152x864` - Landscape
|
||||
- `1440x720` - Wide landscape
|
||||
- `720x1440` - Tall portrait
|
||||
|
||||
## Remember
|
||||
|
||||
- Always use z-ai-web-dev-sdk in backend code only
|
||||
- The SDK is already installed - import as shown
|
||||
- CLI tool is available for quick image generation
|
||||
- Supported sizes are specific - use the provided list
|
||||
- Base64 images need to be decoded before saving
|
||||
- Consider caching for repeated prompts
|
||||
- Implement retry logic for production applications
|
||||
- Use descriptive prompts for better results
|
||||
28
skills/image-generation/scripts/image-generation.ts
Executable file
28
skills/image-generation/scripts/image-generation.ts
Executable file
@@ -0,0 +1,28 @@
|
||||
import ZAI from 'z-ai-web-dev-sdk';
|
||||
import fs from 'fs';
|
||||
|
||||
async function main(prompt: string, size: '1024x1024' | '768x1344' | '864x1152' | '1344x768' | '1152x864' | '1440x720' | '720x1440', outFile: string) {
|
||||
try {
|
||||
const zai = await ZAI.create();
|
||||
|
||||
const response = await zai.images.generations.create({
|
||||
prompt,
|
||||
size
|
||||
});
|
||||
|
||||
const base64 = response?.data?.[0]?.base64;
|
||||
if (!base64) {
|
||||
console.error('No image data returned by the API');
|
||||
console.log('Full response:', JSON.stringify(response, null, 2));
|
||||
return;
|
||||
}
|
||||
|
||||
const buffer = Buffer.from(base64, 'base64');
|
||||
fs.writeFileSync(outFile, buffer);
|
||||
console.log(`Image saved to ${outFile}`);
|
||||
} catch (err: any) {
|
||||
console.error('Image generation failed:', err?.message || err);
|
||||
}
|
||||
}
|
||||
|
||||
main('A cute kitten', '1024x1024', './output.png');
|
||||
30
skills/pdf/LICENSE.txt
Executable file
30
skills/pdf/LICENSE.txt
Executable file
@@ -0,0 +1,30 @@
|
||||
© 2025 Anthropic, PBC. All rights reserved.
|
||||
|
||||
LICENSE: Use of these materials (including all code, prompts, assets, files,
|
||||
and other components of this Skill) is governed by your agreement with
|
||||
Anthropic regarding use of Anthropic's services. If no separate agreement
|
||||
exists, use is governed by Anthropic's Consumer Terms of Service or
|
||||
Commercial Terms of Service, as applicable:
|
||||
https://www.anthropic.com/legal/consumer-terms
|
||||
https://www.anthropic.com/legal/commercial-terms
|
||||
Your applicable agreement is referred to as the "Agreement." "Services" are
|
||||
as defined in the Agreement.
|
||||
|
||||
ADDITIONAL RESTRICTIONS: Notwithstanding anything in the Agreement to the
|
||||
contrary, users may not:
|
||||
|
||||
- Extract these materials from the Services or retain copies of these
|
||||
materials outside the Services
|
||||
- Reproduce or copy these materials, except for temporary copies created
|
||||
automatically during authorized use of the Services
|
||||
- Create derivative works based on these materials
|
||||
- Distribute, sublicense, or transfer these materials to any third party
|
||||
- Make, offer to sell, sell, or import any inventions embodied in these
|
||||
materials
|
||||
- Reverse engineer, decompile, or disassemble these materials
|
||||
|
||||
The receipt, viewing, or possession of these materials does not convey or
|
||||
imply any license or right beyond those expressly granted above.
|
||||
|
||||
Anthropic retains all right, title, and interest in these materials,
|
||||
including all copyrights, patents, and other intellectual property rights.
|
||||
1534
skills/pdf/SKILL.md
Executable file
1534
skills/pdf/SKILL.md
Executable file
File diff suppressed because it is too large
Load Diff
205
skills/pdf/forms.md
Executable file
205
skills/pdf/forms.md
Executable file
@@ -0,0 +1,205 @@
|
||||
**CRITICAL: You MUST complete these steps in order. Do not skip ahead to writing code.**
|
||||
|
||||
If you need to fill out a PDF form, first check to see if the PDF has fillable form fields. Run this script from this file's directory:
|
||||
`python scripts/check_fillable_fields <file.pdf>`, and depending on the result go to either the "Fillable fields" or "Non-fillable fields" and follow those instructions.
|
||||
|
||||
# Fillable fields
|
||||
If the PDF has fillable form fields:
|
||||
- Run this script from this file's directory: `python scripts/extract_form_field_info.py <input.pdf> <field_info.json>`. It will create a JSON file with a list of fields in this format:
|
||||
```
|
||||
[
|
||||
{
|
||||
"field_id": (unique ID for the field),
|
||||
"page": (page number, 1-based),
|
||||
"rect": ([left, bottom, right, top] bounding box in PDF coordinates, y=0 is the bottom of the page),
|
||||
"type": ("text", "checkbox", "radio_group", or "choice"),
|
||||
},
|
||||
// Checkboxes have "checked_value" and "unchecked_value" properties:
|
||||
{
|
||||
"field_id": (unique ID for the field),
|
||||
"page": (page number, 1-based),
|
||||
"type": "checkbox",
|
||||
"checked_value": (Set the field to this value to check the checkbox),
|
||||
"unchecked_value": (Set the field to this value to uncheck the checkbox),
|
||||
},
|
||||
// Radio groups have a "radio_options" list with the possible choices.
|
||||
{
|
||||
"field_id": (unique ID for the field),
|
||||
"page": (page number, 1-based),
|
||||
"type": "radio_group",
|
||||
"radio_options": [
|
||||
{
|
||||
"value": (set the field to this value to select this radio option),
|
||||
"rect": (bounding box for the radio button for this option)
|
||||
},
|
||||
// Other radio options
|
||||
]
|
||||
},
|
||||
// Multiple choice fields have a "choice_options" list with the possible choices:
|
||||
{
|
||||
"field_id": (unique ID for the field),
|
||||
"page": (page number, 1-based),
|
||||
"type": "choice",
|
||||
"choice_options": [
|
||||
{
|
||||
"value": (set the field to this value to select this option),
|
||||
"text": (display text of the option)
|
||||
},
|
||||
// Other choice options
|
||||
],
|
||||
}
|
||||
]
|
||||
```
|
||||
- Convert the PDF to PNGs (one image for each page) with this script (run from this file's directory):
|
||||
`python scripts/convert_pdf_to_images.py <file.pdf> <output_directory>`
|
||||
Then analyze the images to determine the purpose of each form field (make sure to convert the bounding box PDF coordinates to image coordinates).
|
||||
- Create a `field_values.json` file in this format with the values to be entered for each field:
|
||||
```
|
||||
[
|
||||
{
|
||||
"field_id": "last_name", // Must match the field_id from `extract_form_field_info.py`
|
||||
"description": "The user's last name",
|
||||
"page": 1, // Must match the "page" value in field_info.json
|
||||
"value": "Simpson"
|
||||
},
|
||||
{
|
||||
"field_id": "Checkbox12",
|
||||
"description": "Checkbox to be checked if the user is 18 or over",
|
||||
"page": 1,
|
||||
"value": "/On" // If this is a checkbox, use its "checked_value" value to check it. If it's a radio button group, use one of the "value" values in "radio_options".
|
||||
},
|
||||
// more fields
|
||||
]
|
||||
```
|
||||
- Run the `fill_fillable_fields.py` script from this file's directory to create a filled-in PDF:
|
||||
`python scripts/fill_fillable_fields.py <input pdf> <field_values.json> <output pdf>`
|
||||
This script will verify that the field IDs and values you provide are valid; if it prints error messages, correct the appropriate fields and try again.
|
||||
|
||||
# Non-fillable fields
|
||||
If the PDF doesn't have fillable form fields, you'll need to visually determine where the data should be added and create text annotations. Follow the below steps *exactly*. You MUST perform all of these steps to ensure that the the form is accurately completed. Details for each step are below.
|
||||
- Convert the PDF to PNG images and determine field bounding boxes.
|
||||
- Create a JSON file with field information and validation images showing the bounding boxes.
|
||||
- Validate the the bounding boxes.
|
||||
- Use the bounding boxes to fill in the form.
|
||||
|
||||
## Step 1: Visual Analysis (REQUIRED)
|
||||
- Convert the PDF to PNG images. Run this script from this file's directory:
|
||||
`python scripts/convert_pdf_to_images.py <file.pdf> <output_directory>`
|
||||
The script will create a PNG image for each page in the PDF.
|
||||
- Carefully examine each PNG image and identify all form fields and areas where the user should enter data. For each form field where the user should enter text, determine bounding boxes for both the form field label, and the area where the user should enter text. The label and entry bounding boxes MUST NOT INTERSECT; the text entry box should only include the area where data should be entered. Usually this area will be immediately to the side, above, or below its label. Entry bounding boxes must be tall and wide enough to contain their text.
|
||||
|
||||
These are some examples of form structures that you might see:
|
||||
|
||||
*Label inside box*
|
||||
```
|
||||
┌────────────────────────┐
|
||||
│ Name: │
|
||||
└────────────────────────┘
|
||||
```
|
||||
The input area should be to the right of the "Name" label and extend to the edge of the box.
|
||||
|
||||
*Label before line*
|
||||
```
|
||||
Email: _______________________
|
||||
```
|
||||
The input area should be above the line and include its entire width.
|
||||
|
||||
*Label under line*
|
||||
```
|
||||
_________________________
|
||||
Name
|
||||
```
|
||||
The input area should be above the line and include the entire width of the line. This is common for signature and date fields.
|
||||
|
||||
*Label above line*
|
||||
```
|
||||
Please enter any special requests:
|
||||
________________________________________________
|
||||
```
|
||||
The input area should extend from the bottom of the label to the line, and should include the entire width of the line.
|
||||
|
||||
*Checkboxes*
|
||||
```
|
||||
Are you a US citizen? Yes □ No □
|
||||
```
|
||||
For checkboxes:
|
||||
- Look for small square boxes (□) - these are the actual checkboxes to target. They may be to the left or right of their labels.
|
||||
- Distinguish between label text ("Yes", "No") and the clickable checkbox squares.
|
||||
- The entry bounding box should cover ONLY the small square, not the text label.
|
||||
|
||||
### Step 2: Create fields.json and validation images (REQUIRED)
|
||||
- Create a file named `fields.json` with information for the form fields and bounding boxes in this format:
|
||||
```
|
||||
{
|
||||
"pages": [
|
||||
{
|
||||
"page_number": 1,
|
||||
"image_width": (first page image width in pixels),
|
||||
"image_height": (first page image height in pixels),
|
||||
},
|
||||
{
|
||||
"page_number": 2,
|
||||
"image_width": (second page image width in pixels),
|
||||
"image_height": (second page image height in pixels),
|
||||
}
|
||||
// additional pages
|
||||
],
|
||||
"form_fields": [
|
||||
// Example for a text field.
|
||||
{
|
||||
"page_number": 1,
|
||||
"description": "The user's last name should be entered here",
|
||||
// Bounding boxes are [left, top, right, bottom]. The bounding boxes for the label and text entry should not overlap.
|
||||
"field_label": "Last name",
|
||||
"label_bounding_box": [30, 125, 95, 142],
|
||||
"entry_bounding_box": [100, 125, 280, 142],
|
||||
"entry_text": {
|
||||
"text": "Johnson", // This text will be added as an annotation at the entry_bounding_box location
|
||||
"font_size": 14, // optional, defaults to 14
|
||||
"font_color": "000000", // optional, RRGGBB format, defaults to 000000 (black)
|
||||
}
|
||||
},
|
||||
// Example for a checkbox. TARGET THE SQUARE for the entry bounding box, NOT THE TEXT
|
||||
{
|
||||
"page_number": 2,
|
||||
"description": "Checkbox that should be checked if the user is over 18",
|
||||
"entry_bounding_box": [140, 525, 155, 540], // Small box over checkbox square
|
||||
"field_label": "Yes",
|
||||
"label_bounding_box": [100, 525, 132, 540], // Box containing "Yes" text
|
||||
// Use "X" to check a checkbox.
|
||||
"entry_text": {
|
||||
"text": "X",
|
||||
}
|
||||
}
|
||||
// additional form field entries
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Create validation images by running this script from this file's directory for each page:
|
||||
`python scripts/create_validation_image.py <page_number> <path_to_fields.json> <input_image_path> <output_image_path>
|
||||
|
||||
The validation images will have red rectangles where text should be entered, and blue rectangles covering label text.
|
||||
|
||||
### Step 3: Validate Bounding Boxes (REQUIRED)
|
||||
#### Automated intersection check
|
||||
- Verify that none of bounding boxes intersect and that the entry bounding boxes are tall enough by checking the fields.json file with the `check_bounding_boxes.py` script (run from this file's directory):
|
||||
`python scripts/check_bounding_boxes.py <JSON file>`
|
||||
|
||||
If there are errors, reanalyze the relevant fields, adjust the bounding boxes, and iterate until there are no remaining errors. Remember: label (blue) bounding boxes should contain text labels, entry (red) boxes should not.
|
||||
|
||||
#### Manual image inspection
|
||||
**CRITICAL: Do not proceed without visually inspecting validation images**
|
||||
- Red rectangles must ONLY cover input areas
|
||||
- Red rectangles MUST NOT contain any text
|
||||
- Blue rectangles should contain label text
|
||||
- For checkboxes:
|
||||
- Red rectangle MUST be centered on the checkbox square
|
||||
- Blue rectangle should cover the text label for the checkbox
|
||||
|
||||
- If any rectangles look wrong, fix fields.json, regenerate the validation images, and verify again. Repeat this process until the bounding boxes are fully accurate.
|
||||
|
||||
|
||||
### Step 4: Add annotations to the PDF
|
||||
Run this script from this file's directory to create a filled-out PDF using the information in fields.json:
|
||||
`python scripts/fill_pdf_form_with_annotations.py <input_pdf_path> <path_to_fields.json> <output_pdf_path>
|
||||
765
skills/pdf/reference.md
Executable file
765
skills/pdf/reference.md
Executable file
@@ -0,0 +1,765 @@
|
||||
# PDF Processing Advanced Reference
|
||||
|
||||
This document contains advanced PDF processing features, detailed examples, and additional libraries not covered in the main skill instructions.
|
||||
|
||||
## pypdfium2 Library (Apache/BSD License)
|
||||
|
||||
### Overview
|
||||
pypdfium2 is a Python binding for PDFium (Chromium's PDF library). It's excellent for fast PDF rendering, image generation, and serves as a PyMuPDF replacement.
|
||||
|
||||
### Render PDF to Images
|
||||
```python
|
||||
import pypdfium2 as pdfium
|
||||
from PIL import Image
|
||||
|
||||
# Load PDF
|
||||
pdf = pdfium.PdfDocument("document.pdf")
|
||||
|
||||
# Render page to image
|
||||
page = pdf[0] # First page
|
||||
bitmap = page.render(
|
||||
scale=2.0, # Higher resolution
|
||||
rotation=0 # No rotation
|
||||
)
|
||||
|
||||
# Convert to PIL Image
|
||||
img = bitmap.to_pil()
|
||||
img.save("page_1.png", "PNG")
|
||||
|
||||
# Process multiple pages
|
||||
for i, page in enumerate(pdf):
|
||||
bitmap = page.render(scale=1.5)
|
||||
img = bitmap.to_pil()
|
||||
img.save(f"page_{i+1}.jpg", "JPEG", quality=90)
|
||||
```
|
||||
|
||||
### Extract Text with pypdfium2
|
||||
```python
|
||||
import pypdfium2 as pdfium
|
||||
|
||||
pdf = pdfium.PdfDocument("document.pdf")
|
||||
for i, page in enumerate(pdf):
|
||||
text = page.get_text()
|
||||
print(f"Page {i+1} text length: {len(text)} chars")
|
||||
```
|
||||
|
||||
## JavaScript Libraries
|
||||
|
||||
### pdf-lib (MIT License)
|
||||
|
||||
pdf-lib is a powerful JavaScript library for creating and modifying PDF documents in any JavaScript environment.
|
||||
|
||||
#### Load and Manipulate Existing PDF
|
||||
```javascript
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import fs from 'fs';
|
||||
|
||||
async function manipulatePDF() {
|
||||
// Load existing PDF
|
||||
const existingPdfBytes = fs.readFileSync('input.pdf');
|
||||
const pdfDoc = await PDFDocument.load(existingPdfBytes);
|
||||
|
||||
// Get page count
|
||||
const pageCount = pdfDoc.getPageCount();
|
||||
console.log(`Document has ${pageCount} pages`);
|
||||
|
||||
// Add new page
|
||||
const newPage = pdfDoc.addPage([600, 400]);
|
||||
newPage.drawText('Added by pdf-lib', {
|
||||
x: 100,
|
||||
y: 300,
|
||||
size: 16
|
||||
});
|
||||
|
||||
// Save modified PDF
|
||||
const pdfBytes = await pdfDoc.save();
|
||||
fs.writeFileSync('modified.pdf', pdfBytes);
|
||||
}
|
||||
```
|
||||
|
||||
#### Create Complex PDFs from Scratch
|
||||
|
||||
**Note**: This JavaScript example uses pdf-lib's built-in StandardFonts. For Python/reportlab, always use the six registered fonts defined in SKILL.md (SimHei, Microsoft YaHei, SarasaMonoSC, Times New Roman, Calibri, DejaVuSans).
|
||||
|
||||
```javascript
|
||||
import { PDFDocument, rgb, StandardFonts } from 'pdf-lib';
|
||||
import fs from 'fs';
|
||||
|
||||
async function createPDF() {
|
||||
const pdfDoc = await PDFDocument.create();
|
||||
|
||||
// Add fonts
|
||||
const helveticaFont = await pdfDoc.embedFont(StandardFonts.Helvetica);
|
||||
const helveticaBold = await pdfDoc.embedFont(StandardFonts.HelveticaBold);
|
||||
|
||||
// Add page
|
||||
const page = pdfDoc.addPage([595, 842]); // A4 size
|
||||
const { width, height } = page.getSize();
|
||||
|
||||
// Add text with styling
|
||||
page.drawText('Invoice #12345', {
|
||||
x: 50,
|
||||
y: height - 50,
|
||||
size: 18,
|
||||
font: helveticaBold,
|
||||
color: rgb(0.2, 0.2, 0.8)
|
||||
});
|
||||
|
||||
// Add rectangle (header background)
|
||||
page.drawRectangle({
|
||||
x: 40,
|
||||
y: height - 100,
|
||||
width: width - 80,
|
||||
height: 30,
|
||||
color: rgb(0.9, 0.9, 0.9)
|
||||
});
|
||||
|
||||
// Add table-like content
|
||||
const items = [
|
||||
['Item', 'Qty', 'Price', 'Total'],
|
||||
['Widget', '2', '$50', '$100'],
|
||||
['Gadget', '1', '$75', '$75']
|
||||
];
|
||||
|
||||
let yPos = height - 150;
|
||||
items.forEach(row => {
|
||||
let xPos = 50;
|
||||
row.forEach(cell => {
|
||||
page.drawText(cell, {
|
||||
x: xPos,
|
||||
y: yPos,
|
||||
size: 12,
|
||||
font: helveticaFont
|
||||
});
|
||||
xPos += 120;
|
||||
});
|
||||
yPos -= 25;
|
||||
});
|
||||
|
||||
const pdfBytes = await pdfDoc.save();
|
||||
fs.writeFileSync('created.pdf', pdfBytes);
|
||||
}
|
||||
```
|
||||
|
||||
#### Advanced Merge and Split Operations
|
||||
```javascript
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import fs from 'fs';
|
||||
|
||||
async function mergePDFs() {
|
||||
// Create new document
|
||||
const mergedPdf = await PDFDocument.create();
|
||||
|
||||
// Load source PDFs
|
||||
const pdf1Bytes = fs.readFileSync('doc1.pdf');
|
||||
const pdf2Bytes = fs.readFileSync('doc2.pdf');
|
||||
|
||||
const pdf1 = await PDFDocument.load(pdf1Bytes);
|
||||
const pdf2 = await PDFDocument.load(pdf2Bytes);
|
||||
|
||||
// Copy pages from first PDF
|
||||
const pdf1Pages = await mergedPdf.copyPages(pdf1, pdf1.getPageIndices());
|
||||
pdf1Pages.forEach(page => mergedPdf.addPage(page));
|
||||
|
||||
// Copy specific pages from second PDF (pages 0, 2, 4)
|
||||
const pdf2Pages = await mergedPdf.copyPages(pdf2, [0, 2, 4]);
|
||||
pdf2Pages.forEach(page => mergedPdf.addPage(page));
|
||||
|
||||
const mergedPdfBytes = await mergedPdf.save();
|
||||
fs.writeFileSync('merged.pdf', mergedPdfBytes);
|
||||
}
|
||||
```
|
||||
|
||||
### pdfjs-dist (Apache License)
|
||||
|
||||
PDF.js is Mozilla's JavaScript library for rendering PDFs in the browser.
|
||||
|
||||
#### Basic PDF Loading and Rendering
|
||||
```javascript
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
|
||||
// Configure worker (important for performance)
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = './pdf.worker.js';
|
||||
|
||||
async function renderPDF() {
|
||||
// Load PDF
|
||||
const loadingTask = pdfjsLib.getDocument('document.pdf');
|
||||
const pdf = await loadingTask.promise;
|
||||
|
||||
console.log(`Loaded PDF with ${pdf.numPages} pages`);
|
||||
|
||||
// Get first page
|
||||
const page = await pdf.getPage(1);
|
||||
const viewport = page.getViewport({ scale: 1.5 });
|
||||
|
||||
// Render to canvas
|
||||
const canvas = document.createElement('canvas');
|
||||
const context = canvas.getContext('2d');
|
||||
canvas.height = viewport.height;
|
||||
canvas.width = viewport.width;
|
||||
|
||||
const renderContext = {
|
||||
canvasContext: context,
|
||||
viewport: viewport
|
||||
};
|
||||
|
||||
await page.render(renderContext).promise;
|
||||
document.body.appendChild(canvas);
|
||||
}
|
||||
```
|
||||
|
||||
#### Extract Text with Coordinates
|
||||
```javascript
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
|
||||
async function extractText() {
|
||||
const loadingTask = pdfjsLib.getDocument('document.pdf');
|
||||
const pdf = await loadingTask.promise;
|
||||
|
||||
let fullText = '';
|
||||
|
||||
// Extract text from all pages
|
||||
for (let i = 1; i <= pdf.numPages; i++) {
|
||||
const page = await pdf.getPage(i);
|
||||
const textContent = await page.getTextContent();
|
||||
|
||||
const pageText = textContent.items
|
||||
.map(item => item.str)
|
||||
.join(' ');
|
||||
|
||||
fullText += `\n--- Page ${i} ---\n${pageText}`;
|
||||
|
||||
// Get text with coordinates for advanced processing
|
||||
const textWithCoords = textContent.items.map(item => ({
|
||||
text: item.str,
|
||||
x: item.transform[4],
|
||||
y: item.transform[5],
|
||||
width: item.width,
|
||||
height: item.height
|
||||
}));
|
||||
}
|
||||
|
||||
console.log(fullText);
|
||||
return fullText;
|
||||
}
|
||||
```
|
||||
|
||||
#### Extract Annotations and Forms
|
||||
```javascript
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
|
||||
async function extractAnnotations() {
|
||||
const loadingTask = pdfjsLib.getDocument('annotated.pdf');
|
||||
const pdf = await loadingTask.promise;
|
||||
|
||||
for (let i = 1; i <= pdf.numPages; i++) {
|
||||
const page = await pdf.getPage(i);
|
||||
const annotations = await page.getAnnotations();
|
||||
|
||||
annotations.forEach(annotation => {
|
||||
console.log(`Annotation type: ${annotation.subtype}`);
|
||||
console.log(`Content: ${annotation.contents}`);
|
||||
console.log(`Coordinates: ${JSON.stringify(annotation.rect)}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Advanced Command-Line Operations
|
||||
|
||||
### poppler-utils Advanced Features
|
||||
|
||||
#### Extract Text with Bounding Box Coordinates
|
||||
```bash
|
||||
# Extract text with bounding box coordinates (essential for structured data)
|
||||
pdftotext -bbox-layout document.pdf output.xml
|
||||
|
||||
# The XML output contains precise coordinates for each text element
|
||||
```
|
||||
|
||||
#### Advanced Image Conversion
|
||||
```bash
|
||||
# Convert to PNG images with specific resolution
|
||||
pdftoppm -png -r 300 document.pdf output_prefix
|
||||
|
||||
# Convert specific page range with high resolution
|
||||
pdftoppm -png -r 600 -f 1 -l 3 document.pdf high_res_pages
|
||||
|
||||
# Convert to JPEG with quality setting
|
||||
pdftoppm -jpeg -jpegopt quality=85 -r 200 document.pdf jpeg_output
|
||||
```
|
||||
|
||||
#### Extract Embedded Images
|
||||
```bash
|
||||
# Extract all embedded images with metadata
|
||||
pdfimages -j -p document.pdf page_images
|
||||
|
||||
# List image info without extracting
|
||||
pdfimages -list document.pdf
|
||||
|
||||
# Extract images in their original format
|
||||
pdfimages -all document.pdf images/img
|
||||
```
|
||||
|
||||
### qpdf Advanced Features
|
||||
|
||||
#### Complex Page Manipulation
|
||||
```bash
|
||||
# Split PDF into groups of pages
|
||||
qpdf --split-pages=3 input.pdf output_group_%02d.pdf
|
||||
|
||||
# Extract specific pages with complex ranges
|
||||
qpdf input.pdf --pages input.pdf 1,3-5,8,10-end -- extracted.pdf
|
||||
|
||||
# Merge specific pages from multiple PDFs
|
||||
qpdf --empty --pages doc1.pdf 1-3 doc2.pdf 5-7 doc3.pdf 2,4 -- combined.pdf
|
||||
```
|
||||
|
||||
#### PDF Optimization and Repair
|
||||
```bash
|
||||
# Optimize PDF for web (linearize for streaming)
|
||||
qpdf --linearize input.pdf optimized.pdf
|
||||
|
||||
# Remove unused objects and compress
|
||||
qpdf --optimize-level=all input.pdf compressed.pdf
|
||||
|
||||
# Attempt to repair corrupted PDF structure
|
||||
qpdf --check input.pdf
|
||||
qpdf --fix-qdf damaged.pdf repaired.pdf
|
||||
|
||||
# Show detailed PDF structure for debugging
|
||||
qpdf --show-all-pages input.pdf > structure.txt
|
||||
```
|
||||
|
||||
#### Advanced Encryption
|
||||
```bash
|
||||
# Add password protection with specific permissions
|
||||
qpdf --encrypt user_pass owner_pass 256 --print=none --modify=none -- input.pdf encrypted.pdf
|
||||
|
||||
# Check encryption status
|
||||
qpdf --show-encryption encrypted.pdf
|
||||
|
||||
# Remove password protection (requires password)
|
||||
qpdf --password=secret123 --decrypt encrypted.pdf decrypted.pdf
|
||||
```
|
||||
|
||||
## Advanced Python Techniques
|
||||
|
||||
### pdfplumber Advanced Features
|
||||
|
||||
#### Extract Text with Precise Coordinates
|
||||
```python
|
||||
import pdfplumber
|
||||
|
||||
with pdfplumber.open("document.pdf") as pdf:
|
||||
page = pdf.pages[0]
|
||||
|
||||
# Extract all text with coordinates
|
||||
chars = page.chars
|
||||
for char in chars[:10]: # First 10 characters
|
||||
print(f"Char: '{char['text']}' at x:{char['x0']:.1f} y:{char['y0']:.1f}")
|
||||
|
||||
# Extract text by bounding box (left, top, right, bottom)
|
||||
bbox_text = page.within_bbox((100, 100, 400, 200)).extract_text()
|
||||
```
|
||||
|
||||
#### Advanced Table Extraction with Custom Settings
|
||||
```python
|
||||
import pdfplumber
|
||||
import pandas as pd
|
||||
|
||||
with pdfplumber.open("complex_table.pdf") as pdf:
|
||||
page = pdf.pages[0]
|
||||
|
||||
# Extract tables with custom settings for complex layouts
|
||||
table_settings = {
|
||||
"vertical_strategy": "lines",
|
||||
"horizontal_strategy": "lines",
|
||||
"snap_tolerance": 3,
|
||||
"intersection_tolerance": 15
|
||||
}
|
||||
tables = page.extract_tables(table_settings)
|
||||
|
||||
# Visual debugging for table extraction
|
||||
img = page.to_image(resolution=150)
|
||||
img.save("debug_layout.png")
|
||||
```
|
||||
|
||||
### reportlab Advanced Features
|
||||
|
||||
#### Quick TOC Template (Copy-Paste Ready)
|
||||
|
||||
```python
|
||||
from reportlab.lib.pagesizes import A4
|
||||
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, PageBreak
|
||||
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
||||
from reportlab.lib import colors
|
||||
from reportlab.lib.units import inch
|
||||
from reportlab.pdfbase import pdfmetrics
|
||||
from reportlab.pdfbase.ttfonts import TTFont
|
||||
from reportlab.pdfbase.pdfmetrics import registerFontFamily
|
||||
|
||||
# Register fonts first
|
||||
pdfmetrics.registerFont(TTFont('Times New Roman', '/usr/share/fonts/truetype/english/Times-New-Roman.ttf'))
|
||||
registerFontFamily('Times New Roman', normal='Times New Roman', bold='Times New Roman')
|
||||
|
||||
# Setup
|
||||
doc = SimpleDocTemplate("report.pdf", pagesize=A4,
|
||||
leftMargin=0.75*inch, rightMargin=0.75*inch)
|
||||
styles = getSampleStyleSheet()
|
||||
|
||||
# Configure heading style
|
||||
styles['Heading1'].fontName = 'Times New Roman'
|
||||
styles['Heading1'].textColor = colors.black # Titles must be black
|
||||
|
||||
story = []
|
||||
|
||||
# Calculate dimensions
|
||||
page_width = A4[0]
|
||||
available_width = page_width - 1.5*inch
|
||||
page_num_width = 50 # Fixed width for page numbers (enough for 3-4 digits)
|
||||
|
||||
# Calculate dots: fill space from title to page number
|
||||
dots_column_width = available_width - 200 - page_num_width # Reserve space for title + page
|
||||
optimal_dot_count = int(dots_column_width / 4.5) # ~4.5pt per dot at 7pt font
|
||||
|
||||
# Define styles
|
||||
toc_style = ParagraphStyle('TOCEntry', parent=styles['Normal'],
|
||||
fontName='Times New Roman', fontSize=11, leading=16)
|
||||
dots_style = ParagraphStyle('LeaderDots', parent=styles['Normal'],
|
||||
fontName='Times New Roman', fontSize=7, leading=16) # Smaller font for more dots
|
||||
|
||||
# Build TOC (use Paragraph with <b></b> for bold heading)
|
||||
toc_data = [
|
||||
[Paragraph('<b>Table of Contents</b>', styles['Heading1']), '', ''],
|
||||
['', '', ''],
|
||||
]
|
||||
|
||||
entries = [('Section 1', '5'), ('Section 2', '10')]
|
||||
for title, page in entries:
|
||||
toc_data.append([
|
||||
Paragraph(title, toc_style),
|
||||
Paragraph('.' * optimal_dot_count, dots_style),
|
||||
Paragraph(page, toc_style)
|
||||
])
|
||||
|
||||
# Use None for title column (auto-expand), fixed for others
|
||||
toc_table = Table(toc_data, colWidths=[None, dots_column_width, page_num_width])
|
||||
toc_table.setStyle(TableStyle([
|
||||
('GRID', (0, 0), (-1, -1), 0, colors.white),
|
||||
('LINEBELOW', (0, 0), (0, 0), 1.5, colors.black),
|
||||
('ALIGN', (0, 0), (0, -1), 'LEFT'),
|
||||
('ALIGN', (1, 0), (1, -1), 'LEFT'),
|
||||
('ALIGN', (2, 0), (2, -1), 'RIGHT'),
|
||||
('VALIGN', (0, 0), (-1, -1), 'TOP'),
|
||||
('LEFTPADDING', (0, 0), (-1, -1), 0),
|
||||
('RIGHTPADDING', (0, 0), (-1, -1), 0),
|
||||
('TOPPADDING', (0, 2), (-1, -1), 3),
|
||||
('BOTTOMPADDING', (0, 2), (-1, -1), 3),
|
||||
('TEXTCOLOR', (1, 2), (1, -1), colors.HexColor('#888888')),
|
||||
]))
|
||||
|
||||
story.append(toc_table)
|
||||
story.append(PageBreak())
|
||||
|
||||
doc.build(story)
|
||||
```
|
||||
|
||||
#### Advanced: Table of Contents with Leader Dots
|
||||
|
||||
**Critical Rules for TOC with Leader Dots:**
|
||||
|
||||
1. **Three-column structure**: [Title, Dots, Page Number] for leader dot style
|
||||
2. **Column width strategy**:
|
||||
- Title: `None` (auto-expands to content)
|
||||
- Dots: Calculated width = `available_width - 200 - 50` (reserves space for title + page)
|
||||
- Page number: Fixed `50pt` (enough for 3-4 digit numbers, ensures right alignment)
|
||||
3. **Dynamic dot count**: `int(dots_column_width / 4.5)` for 7pt font (adjust based on font size)
|
||||
4. **Dot styling**: Small font (7-8pt) and gray color (#888888) for professional look
|
||||
5. **Alignment sequence**: LEFT (title) → LEFT (dots flow from title) → RIGHT (page numbers)
|
||||
6. **Zero padding**: Essential for seamless visual connection between columns
|
||||
7. **Indentation**: Use leading spaces in title text for hierarchy (e.g., " 1.1 Subsection")
|
||||
|
||||
**MANDATORY STYLE REQUIREMENTS:**
|
||||
- ✅ USE FIXED WIDTHS: Percentage-based widths are STRICTLY FORBIDDEN. You MUST use fixed values to guarantee alignment, especially for page numbers.
|
||||
- ✅ DYNAMIC LEADER DOTS: Hard-coded dot counts are STRICTLY FORBIDDEN. You MUST calculate the number of dots dynamically based on the column width to prevent overflow or wrapping.
|
||||
- ✅ MINIMUM COLUMN WIDTH: The page number column MUST be at least 40pt wide. Anything less will prevent proper right alignment.
|
||||
- ✅ DOT FONT SIZE: Leader dot font size MUST NOT EXCEED 8pt. Larger sizes will ruin the dot density and are unacceptable.
|
||||
- ✅ DOT ALIGNMENT: Dots MUST remain left-aligned to maintain the visual flow from the title. Right-aligning dots is forbidden.
|
||||
- ✅ ZERO PADDING: Padding between columns MUST be set to exactly 0. Any gap will create a break in the dot line and is not allowed.
|
||||
- ✅ USE PARAGRAPH OBJECTS: Bold text MUST be wrapped in a Paragraph() object like `Paragraph('<b>Text</b>', style)`. Using plain strings like `'<b>Text</b>'` is strictly STRICTLY FORBIDDEN as styles will not render.
|
||||
|
||||
#### CRITICAL: Table Cell Content Must Use Paragraph
|
||||
|
||||
**ALL text content in table cells MUST be wrapped in `Paragraph()` objects.** This is essential for:
|
||||
- Rendering formatting tags (`<b>`, `<super>`, `<sub>`, `<i>`)
|
||||
- Proper font application
|
||||
- Correct text alignment within cells
|
||||
- Consistent styling across the table
|
||||
|
||||
**The ONLY exception**: `Image()` objects can be placed directly in table cells without Paragraph wrapping.
|
||||
|
||||
```python
|
||||
from reportlab.platypus import Table, TableStyle, Paragraph, Image
|
||||
from reportlab.lib.styles import ParagraphStyle
|
||||
from reportlab.lib import colors
|
||||
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT
|
||||
|
||||
# Define cell styles
|
||||
header_style = ParagraphStyle(
|
||||
name='TableHeader',
|
||||
fontName='Times New Roman',
|
||||
fontSize=11,
|
||||
textColor=colors.white,
|
||||
alignment=TA_CENTER
|
||||
)
|
||||
|
||||
cell_style = ParagraphStyle(
|
||||
name='TableCell',
|
||||
fontName='Times New Roman',
|
||||
fontSize=10,
|
||||
textColor=colors.black,
|
||||
alignment=TA_CENTER
|
||||
)
|
||||
|
||||
# ✅ CORRECT: All text wrapped in Paragraph()
|
||||
data = [
|
||||
[
|
||||
Paragraph('<b>Name</b>', header_style),
|
||||
Paragraph('<b>Formula</b>', header_style),
|
||||
Paragraph('<b>Value</b>', header_style)
|
||||
],
|
||||
[
|
||||
Paragraph('Water', cell_style),
|
||||
Paragraph('H<sub>2</sub>O', cell_style), # Subscript works
|
||||
Paragraph('18.015 g/mol', cell_style)
|
||||
],
|
||||
[
|
||||
Paragraph('Pressure', cell_style),
|
||||
Paragraph('1.01 x 10<super>5</super> Pa', cell_style), # Superscript works
|
||||
Paragraph('<b>Standard</b>', cell_style) # Bold works
|
||||
]
|
||||
]
|
||||
|
||||
# ❌ WRONG: Plain strings - NO formatting will render
|
||||
# data = [
|
||||
# ['<b>Name</b>', '<b>Formula</b>', '<b>Value</b>'], # Bold won't work!
|
||||
# ['Water', 'H<sub>2</sub>O', '18.015 g/mol'], # Subscript won't work!
|
||||
# ]
|
||||
|
||||
# Image exception - Image objects go directly, no Paragraph needed
|
||||
# data_with_image = [
|
||||
# [Paragraph('<b>Logo</b>', header_style), Paragraph('<b>Description</b>', header_style)],
|
||||
# [Image('logo.png', width=50, height=50), Paragraph('Company logo', cell_style)],
|
||||
# ]
|
||||
|
||||
table = Table(data, colWidths=[100, 150, 100])
|
||||
table.setStyle(TableStyle([
|
||||
('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#1F4E79')),
|
||||
('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
|
||||
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
|
||||
]))
|
||||
```
|
||||
|
||||
#### Debug Tips for Layout Issues
|
||||
|
||||
```python
|
||||
from reportlab.platypus import HRFlowable
|
||||
from reportlab.lib.colors import red
|
||||
|
||||
# Visualize spacing during development
|
||||
story.append(table)
|
||||
story.append(HRFlowable(width="100%", color=red, thickness=0.5, spaceBefore=0, spaceAfter=0))
|
||||
story.append(Spacer(1, 6))
|
||||
story.append(HRFlowable(width="100%", color=red, thickness=0.5, spaceBefore=0, spaceAfter=0))
|
||||
story.append(caption)
|
||||
# This creates visual markers to see actual spacing
|
||||
```
|
||||
|
||||
## Complex Workflows
|
||||
|
||||
### Extract Figures/Images from PDF
|
||||
|
||||
#### Method 1: Using pdfimages (fastest)
|
||||
```bash
|
||||
# Extract all images with original quality
|
||||
pdfimages -all document.pdf images/img
|
||||
```
|
||||
|
||||
#### Method 2: Using pypdfium2 + Image Processing
|
||||
```python
|
||||
import pypdfium2 as pdfium
|
||||
from PIL import Image
|
||||
import numpy as np
|
||||
|
||||
def extract_figures(pdf_path, output_dir):
|
||||
pdf = pdfium.PdfDocument(pdf_path)
|
||||
|
||||
for page_num, page in enumerate(pdf):
|
||||
# Render high-resolution page
|
||||
bitmap = page.render(scale=3.0)
|
||||
img = bitmap.to_pil()
|
||||
|
||||
# Convert to numpy for processing
|
||||
img_array = np.array(img)
|
||||
|
||||
# Simple figure detection (non-white regions)
|
||||
mask = np.any(img_array != [255, 255, 255], axis=2)
|
||||
|
||||
# Find contours and extract bounding boxes
|
||||
# (This is simplified - real implementation would need more sophisticated detection)
|
||||
|
||||
# Save detected figures
|
||||
# ... implementation depends on specific needs
|
||||
```
|
||||
|
||||
### Batch PDF Processing with Error Handling
|
||||
```python
|
||||
import os
|
||||
import glob
|
||||
from pypdf import PdfReader, PdfWriter
|
||||
import logging
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def batch_process_pdfs(input_dir, operation='merge'):
|
||||
pdf_files = glob.glob(os.path.join(input_dir, "*.pdf"))
|
||||
|
||||
if operation == 'merge':
|
||||
writer = PdfWriter()
|
||||
for pdf_file in pdf_files:
|
||||
try:
|
||||
reader = PdfReader(pdf_file)
|
||||
for page in reader.pages:
|
||||
writer.add_page(page)
|
||||
logger.info(f"Processed: {pdf_file}")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to process {pdf_file}: {e}")
|
||||
continue
|
||||
|
||||
with open("batch_merged.pdf", "wb") as output:
|
||||
writer.write(output)
|
||||
|
||||
elif operation == 'extract_text':
|
||||
for pdf_file in pdf_files:
|
||||
try:
|
||||
reader = PdfReader(pdf_file)
|
||||
text = ""
|
||||
for page in reader.pages:
|
||||
text += page.extract_text()
|
||||
|
||||
output_file = pdf_file.replace('.pdf', '.txt')
|
||||
with open(output_file, 'w', encoding='utf-8') as f:
|
||||
f.write(text)
|
||||
logger.info(f"Extracted text from: {pdf_file}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to extract text from {pdf_file}: {e}")
|
||||
continue
|
||||
```
|
||||
|
||||
### Advanced PDF Cropping
|
||||
```python
|
||||
from pypdf import PdfWriter, PdfReader
|
||||
|
||||
reader = PdfReader("input.pdf")
|
||||
writer = PdfWriter()
|
||||
|
||||
# Crop page (left, bottom, right, top in points)
|
||||
page = reader.pages[0]
|
||||
page.mediabox.left = 50
|
||||
page.mediabox.bottom = 50
|
||||
page.mediabox.right = 550
|
||||
page.mediabox.top = 750
|
||||
|
||||
writer.add_page(page)
|
||||
with open("cropped.pdf", "wb") as output:
|
||||
writer.write(output)
|
||||
```
|
||||
|
||||
## Performance Optimization Tips
|
||||
|
||||
### 1. For Large PDFs
|
||||
- Use streaming approaches instead of loading entire PDF in memory
|
||||
- Use `qpdf --split-pages` for splitting large files
|
||||
- Process pages individually with pypdfium2
|
||||
|
||||
### 2. For Text Extraction
|
||||
- `pdftotext -bbox-layout` is fastest for plain text extraction
|
||||
- Use pdfplumber for structured data and tables
|
||||
- Avoid `pypdf.extract_text()` for very large documents
|
||||
|
||||
### 3. For Image Extraction
|
||||
- `pdfimages` is much faster than rendering pages
|
||||
- Use low resolution for previews, high resolution for final output
|
||||
|
||||
### 4. For Form Filling
|
||||
- pdf-lib maintains form structure better than most alternatives
|
||||
- Pre-validate form fields before processing
|
||||
|
||||
### 5. Memory Management
|
||||
```python
|
||||
# Process PDFs in chunks
|
||||
def process_large_pdf(pdf_path, chunk_size=10):
|
||||
reader = PdfReader(pdf_path)
|
||||
total_pages = len(reader.pages)
|
||||
|
||||
for start_idx in range(0, total_pages, chunk_size):
|
||||
end_idx = min(start_idx + chunk_size, total_pages)
|
||||
writer = PdfWriter()
|
||||
|
||||
for i in range(start_idx, end_idx):
|
||||
writer.add_page(reader.pages[i])
|
||||
|
||||
# Process chunk
|
||||
with open(f"chunk_{start_idx//chunk_size}.pdf", "wb") as output:
|
||||
writer.write(output)
|
||||
```
|
||||
|
||||
## Troubleshooting Common Issues
|
||||
|
||||
### Encrypted PDFs
|
||||
```python
|
||||
# Handle password-protected PDFs
|
||||
from pypdf import PdfReader
|
||||
|
||||
try:
|
||||
reader = PdfReader("encrypted.pdf")
|
||||
if reader.is_encrypted:
|
||||
reader.decrypt("password")
|
||||
except Exception as e:
|
||||
print(f"Failed to decrypt: {e}")
|
||||
```
|
||||
|
||||
### Corrupted PDFs
|
||||
```bash
|
||||
# Use qpdf to repair
|
||||
qpdf --check corrupted.pdf
|
||||
qpdf --replace-input corrupted.pdf
|
||||
```
|
||||
|
||||
### Text Extraction Issues
|
||||
```python
|
||||
# Fallback to OCR for scanned PDFs
|
||||
import pytesseract
|
||||
from pdf2image import convert_from_path
|
||||
|
||||
def extract_text_with_ocr(pdf_path):
|
||||
images = convert_from_path(pdf_path)
|
||||
text = ""
|
||||
for i, image in enumerate(images):
|
||||
text += pytesseract.image_to_string(image)
|
||||
return text
|
||||
```
|
||||
|
||||
## License Information
|
||||
|
||||
- **pypdf**: BSD License
|
||||
- **pdfplumber**: MIT License
|
||||
- **pypdfium2**: Apache/BSD License
|
||||
- **reportlab**: BSD License
|
||||
- **poppler-utils**: GPL-2 License
|
||||
- **qpdf**: Apache License
|
||||
- **pdf-lib**: MIT License
|
||||
- **pdfjs-dist**: Apache License
|
||||
172
skills/pdf/scripts/add_zai_metadata.py
Executable file
172
skills/pdf/scripts/add_zai_metadata.py
Executable file
@@ -0,0 +1,172 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Add Z.ai branding metadata to PDF documents.
|
||||
|
||||
This script adds Z.ai metadata (Author, Creator, Producer) to PDF files.
|
||||
It can process single files or batch process multiple PDFs.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
from pypdf import PdfReader, PdfWriter
|
||||
|
||||
|
||||
def add_zai_metadata(input_pdf_path, output_pdf_path=None, custom_title=None, verbose=True):
|
||||
"""
|
||||
Add Z.ai branding metadata to a PDF document.
|
||||
|
||||
Args:
|
||||
input_pdf_path: Path to input PDF
|
||||
output_pdf_path: Path to output PDF (default: overwrites input)
|
||||
custom_title: Custom title to use (default: preserves original or uses filename)
|
||||
verbose: Print status messages (default: True)
|
||||
|
||||
Sets:
|
||||
- Author: Z.ai
|
||||
- Creator: Z.ai
|
||||
- Producer: http://z.ai
|
||||
- Title: Custom title, original title, or filename (in that priority)
|
||||
|
||||
Returns:
|
||||
Path to the output PDF file
|
||||
"""
|
||||
# Validate input file exists
|
||||
if not os.path.exists(input_pdf_path):
|
||||
print(f"Error: Input file not found: {input_pdf_path}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Read the PDF
|
||||
try:
|
||||
reader = PdfReader(input_pdf_path)
|
||||
except Exception as e:
|
||||
print(f"Error: Cannot open PDF: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
writer = PdfWriter()
|
||||
|
||||
# Copy all pages
|
||||
for page in reader.pages:
|
||||
writer.add_page(page)
|
||||
|
||||
# Determine title
|
||||
if custom_title:
|
||||
title = custom_title
|
||||
else:
|
||||
original_meta = reader.metadata
|
||||
if original_meta and original_meta.title and original_meta.title not in ['(anonymous)', 'unspecified', None]:
|
||||
title = original_meta.title
|
||||
else:
|
||||
# Use filename without extension as title
|
||||
title = os.path.splitext(os.path.basename(input_pdf_path))[0]
|
||||
|
||||
# Add Z.ai metadata
|
||||
writer.add_metadata({
|
||||
'/Title': title,
|
||||
'/Author': 'Z.ai',
|
||||
'/Creator': 'Z.ai',
|
||||
'/Producer': 'http://z.ai',
|
||||
})
|
||||
|
||||
# Write output
|
||||
if output_pdf_path is None:
|
||||
output_pdf_path = input_pdf_path
|
||||
|
||||
try:
|
||||
with open(output_pdf_path, "wb") as output:
|
||||
writer.write(output)
|
||||
except Exception as e:
|
||||
print(f"Error: Cannot write output file: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Print status
|
||||
if verbose:
|
||||
print(f"✓ Updated metadata for: {os.path.basename(input_pdf_path)}")
|
||||
print(f" Title: {title}")
|
||||
print(f" Author: Z.ai")
|
||||
print(f" Creator: Z.ai")
|
||||
print(f" Producer: http://z.ai")
|
||||
if output_pdf_path != input_pdf_path:
|
||||
print(f" Output: {output_pdf_path}")
|
||||
|
||||
return output_pdf_path
|
||||
|
||||
|
||||
def main():
|
||||
"""Command-line interface for add_zai_metadata."""
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Add Z.ai branding metadata to PDF documents',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Examples:
|
||||
# Add metadata to a single PDF (in-place)
|
||||
%(prog)s document.pdf
|
||||
|
||||
# Add metadata to a single PDF (create new file)
|
||||
%(prog)s input.pdf -o output.pdf
|
||||
|
||||
# Add metadata with custom title
|
||||
%(prog)s report.pdf -t "Q4 Financial Analysis"
|
||||
|
||||
# Batch process all PDFs in current directory
|
||||
%(prog)s *.pdf
|
||||
|
||||
# Quiet mode (no output)
|
||||
%(prog)s document.pdf -q
|
||||
"""
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'input',
|
||||
nargs='+',
|
||||
help='Input PDF file(s) to process'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'-o', '--output',
|
||||
help='Output PDF path (only for single input file)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'-t', '--title',
|
||||
help='Custom title for the PDF'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'-q', '--quiet',
|
||||
action='store_true',
|
||||
help='Quiet mode (no status messages)'
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Check if output is specified for multiple files
|
||||
if args.output and len(args.input) > 1:
|
||||
print("Error: --output can only be used with a single input file", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Process each input file
|
||||
for input_path in args.input:
|
||||
# Determine output path
|
||||
if len(args.input) == 1 and args.output:
|
||||
output_path = args.output
|
||||
else:
|
||||
output_path = None # Overwrite in-place
|
||||
|
||||
# Determine title
|
||||
if args.title:
|
||||
custom_title = args.title
|
||||
else:
|
||||
custom_title = None
|
||||
|
||||
# Add metadata
|
||||
add_zai_metadata(
|
||||
input_path,
|
||||
output_pdf_path=output_path,
|
||||
custom_title=custom_title,
|
||||
verbose=not args.quiet
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
70
skills/pdf/scripts/check_bounding_boxes.py
Executable file
70
skills/pdf/scripts/check_bounding_boxes.py
Executable file
@@ -0,0 +1,70 @@
|
||||
from dataclasses import dataclass
|
||||
import json
|
||||
import sys
|
||||
|
||||
|
||||
# Script to check that the `fields.json` file that GLM creates when analyzing PDFs
|
||||
# does not have overlapping bounding boxes. See forms.md.
|
||||
|
||||
|
||||
@dataclass
|
||||
class RectAndField:
|
||||
rect: list[float]
|
||||
rect_type: str
|
||||
field: dict
|
||||
|
||||
|
||||
# Returns a list of messages that are printed to stdout for GLM to read.
|
||||
def get_bounding_box_messages(fields_json_stream) -> list[str]:
|
||||
messages = []
|
||||
fields = json.load(fields_json_stream)
|
||||
messages.append(f"Read {len(fields['form_fields'])} fields")
|
||||
|
||||
def rects_intersect(r1, r2):
|
||||
disjoint_horizontal = r1[0] >= r2[2] or r1[2] <= r2[0]
|
||||
disjoint_vertical = r1[1] >= r2[3] or r1[3] <= r2[1]
|
||||
return not (disjoint_horizontal or disjoint_vertical)
|
||||
|
||||
rects_and_fields = []
|
||||
for f in fields["form_fields"]:
|
||||
rects_and_fields.append(RectAndField(f["label_bounding_box"], "label", f))
|
||||
rects_and_fields.append(RectAndField(f["entry_bounding_box"], "entry", f))
|
||||
|
||||
has_error = False
|
||||
for i, ri in enumerate(rects_and_fields):
|
||||
# This is O(N^2); we can optimize if it becomes a problem.
|
||||
for j in range(i + 1, len(rects_and_fields)):
|
||||
rj = rects_and_fields[j]
|
||||
if ri.field["page_number"] == rj.field["page_number"] and rects_intersect(ri.rect, rj.rect):
|
||||
has_error = True
|
||||
if ri.field is rj.field:
|
||||
messages.append(f"FAILURE: intersection between label and entry bounding boxes for `{ri.field['description']}` ({ri.rect}, {rj.rect})")
|
||||
else:
|
||||
messages.append(f"FAILURE: intersection between {ri.rect_type} bounding box for `{ri.field['description']}` ({ri.rect}) and {rj.rect_type} bounding box for `{rj.field['description']}` ({rj.rect})")
|
||||
if len(messages) >= 20:
|
||||
messages.append("Aborting further checks; fix bounding boxes and try again")
|
||||
return messages
|
||||
if ri.rect_type == "entry":
|
||||
if "entry_text" in ri.field:
|
||||
font_size = ri.field["entry_text"].get("font_size", 14)
|
||||
entry_height = ri.rect[3] - ri.rect[1]
|
||||
if entry_height < font_size:
|
||||
has_error = True
|
||||
messages.append(f"FAILURE: entry bounding box height ({entry_height}) for `{ri.field['description']}` is too short for the text content (font size: {font_size}). Increase the box height or decrease the font size.")
|
||||
if len(messages) >= 20:
|
||||
messages.append("Aborting further checks; fix bounding boxes and try again")
|
||||
return messages
|
||||
|
||||
if not has_error:
|
||||
messages.append("SUCCESS: All bounding boxes are valid")
|
||||
return messages
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) != 2:
|
||||
print("Usage: check_bounding_boxes.py [fields.json]")
|
||||
sys.exit(1)
|
||||
# Input file should be in the `fields.json` format described in forms.md.
|
||||
with open(sys.argv[1]) as f:
|
||||
messages = get_bounding_box_messages(f)
|
||||
for msg in messages:
|
||||
print(msg)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user