Compare commits
149 Commits
585b9bd720
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f3d011951b | ||
| 7dc76d7b59 | |||
| b2dec2667a | |||
|
|
879d31b51d | ||
|
|
473a5f7f06 | ||
| 2eec988c56 | |||
| 8820048d55 | |||
| 6af7720470 | |||
|
|
5f134945ab | ||
| bc12cc1b08 | |||
| 17b8ea7192 | |||
|
|
2bfdd686c7 | ||
|
|
066cfaba46 | ||
| e8850e85fc | |||
| d083a3123a | |||
| 96c1927f8d | |||
| bdac9d0709 | |||
| 8faba1ea21 | |||
| dd9b77f6bb | |||
| d45955f6de | |||
| f905915f34 | |||
|
|
52951d7296 | ||
| 3c47979913 | |||
| 9aad809322 | |||
| b7850e5b8a | |||
|
|
effcdfbbe6 | ||
| 4277a369d2 | |||
| cf3f971741 | |||
| 75737cf95c | |||
| 4b544dc214 | |||
| 597e621b69 | |||
| 725ac4b76a | |||
| 8e8b35faa4 | |||
| 664ee0312c | |||
| cceaf7fb07 | |||
| d5d638b60b | |||
| 8de5ae3a4f | |||
| 8f20c48baa | |||
|
|
547cccbeb7 | ||
| 86e665bcae | |||
|
|
d1aa91f727 | ||
| bc021924e4 | |||
| 6179a89b6c | |||
| 7c12028f63 | |||
| befb4739ee | |||
| fe07cee58c | |||
|
|
066c457d90 | ||
| 39b608dfd0 | |||
| 6b600b44ca | |||
|
|
b26ad75299 | ||
| b69f312611 | |||
| c65db9abc3 | |||
| 1b4ad5e710 | |||
| e46e2be830 | |||
| f515b90c43 | |||
| 6aff10e240 | |||
|
|
5d02da03b4 | ||
| d99188bfb9 | |||
| c3776c642b | |||
| 46a99ecd55 | |||
| 81744b9b9e | |||
| 469b325f0e | |||
| 8a3fe5461e | |||
| b65841c0cc | |||
| 8ef334ba1b | |||
|
|
2492daa0ad | ||
| 8af06f6916 | |||
| 7008fb007f | |||
| dc039fcced | |||
|
|
fcb1d771f4 | ||
| 30ca81090a | |||
| e722841e60 | |||
| b4ab67aed9 | |||
| 6a8f82bb2e | |||
| 09761c8ce8 | |||
|
|
5e3affcf3a | ||
|
|
455f7938be | ||
|
|
9525b1d927 | ||
| 8810c678c9 | |||
| cd3155e63c | |||
| 45fdca65a7 | |||
| 491db8bc03 | |||
| c735bc3a78 | |||
| e2db4bd3a5 | |||
| fc0f5a11be | |||
| c5528ce1b7 | |||
| 9116ea4a84 | |||
| ce8b0b16b1 | |||
| 259c62b6b4 | |||
| 208b8fc41d | |||
| c611c0ce6f | |||
| 25c266babb | |||
| 04ad139eae | |||
|
|
6add091a7b | ||
|
|
a05b3a8d3c | ||
| 35bc735ecc | |||
|
|
fcd2d03424 | ||
| 87c7981ad9 | |||
| 9edf8936ba | |||
|
|
753bd8bb4b | ||
| 3e09b4cc10 | |||
| 55970622f1 | |||
| 98164c65a2 | |||
|
|
d63a34f4e6 | ||
| 20ab9f890f | |||
| 038213a26c | |||
| ff41aa9c04 | |||
| f60e070984 | |||
| 3f7169844c | |||
|
|
8b993d5ddd | ||
|
|
9cfa9a3417 | ||
|
|
b200f80d88 | ||
|
|
e57baaead6 | ||
|
|
2576f62f88 | ||
|
|
cd24fe007f | ||
|
|
3898880665 | ||
| 562e618aaa | |||
| ec4d57ea69 | |||
|
|
7a5b26607e | ||
|
|
50ceb98e83 | ||
|
|
2065395bd2 | ||
|
|
517dc41f01 | ||
|
|
6f8e677045 | ||
|
|
1747291f41 | ||
|
|
cff1e0145b | ||
|
|
3ab7ea1898 | ||
|
|
c5d75f053b | ||
|
|
730476e927 | ||
| d8a4487b2b | |||
| c93a5f69d8 | |||
|
|
b826afb17c | ||
|
|
2a5a157c57 | ||
|
|
ca9b145d3e | ||
|
|
7676f03c96 | ||
|
|
b5b91d8971 | ||
|
|
f1bddf3fbe | ||
| b1c966f69f | |||
| dba6350493 | |||
|
|
6fb5b5993a | ||
|
|
4c2b015210 | ||
|
|
9f9f193287 | ||
|
|
d34a314f02 | ||
| 8d45cfe9db | |||
| f9d897c081 | |||
| d2616ac2f9 | |||
| d2a6780c23 | |||
| fc32b83980 | |||
|
|
b936654a11 | ||
| 2a525f95b9 |
@@ -1,386 +0,0 @@
|
||||
---
|
||||
name: ui-ux-pro-max
|
||||
description: "UI/UX design intelligence. 50 styles, 21 palettes, 50 font pairings, 20 charts, 9 stacks (React, Next.js, Vue, Svelte, SwiftUI, React Native, Flutter, Tailwind, shadcn/ui). Actions: plan, build, create, design, implement, review, fix, improve, optimize, enhance, refactor, check UI/UX code. Projects: website, landing page, dashboard, admin panel, e-commerce, SaaS, portfolio, blog, mobile app, .html, .tsx, .vue, .svelte. Elements: button, modal, navbar, sidebar, card, table, form, chart. Styles: glassmorphism, claymorphism, minimalism, brutalism, neumorphism, bento grid, dark mode, responsive, skeuomorphism, flat design. Topics: color palette, accessibility, animation, layout, typography, font pairing, spacing, hover, shadow, gradient. Integrations: shadcn/ui MCP for component search and examples."
|
||||
---
|
||||
|
||||
# UI/UX Pro Max - Design Intelligence
|
||||
|
||||
Comprehensive design guide for web and mobile applications. Contains 50+ styles, 97 color palettes, 57 font pairings, 99 UX guidelines, and 25 chart types across 9 technology stacks. Searchable database with priority-based recommendations.
|
||||
|
||||
## When to Apply
|
||||
|
||||
Reference these guidelines when:
|
||||
- Designing new UI components or pages
|
||||
- Choosing color palettes and typography
|
||||
- Reviewing code for UX issues
|
||||
- Building landing pages or dashboards
|
||||
- Implementing accessibility requirements
|
||||
|
||||
## Rule Categories by Priority
|
||||
|
||||
| Priority | Category | Impact | Domain |
|
||||
|----------|----------|--------|--------|
|
||||
| 1 | Accessibility | CRITICAL | `ux` |
|
||||
| 2 | Touch & Interaction | CRITICAL | `ux` |
|
||||
| 3 | Performance | HIGH | `ux` |
|
||||
| 4 | Layout & Responsive | HIGH | `ux` |
|
||||
| 5 | Typography & Color | MEDIUM | `typography`, `color` |
|
||||
| 6 | Animation | MEDIUM | `ux` |
|
||||
| 7 | Style Selection | MEDIUM | `style`, `product` |
|
||||
| 8 | Charts & Data | LOW | `chart` |
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### 1. Accessibility (CRITICAL)
|
||||
|
||||
- `color-contrast` - Minimum 4.5:1 ratio for normal text
|
||||
- `focus-states` - Visible focus rings on interactive elements
|
||||
- `alt-text` - Descriptive alt text for meaningful images
|
||||
- `aria-labels` - aria-label for icon-only buttons
|
||||
- `keyboard-nav` - Tab order matches visual order
|
||||
- `form-labels` - Use label with for attribute
|
||||
|
||||
### 2. Touch & Interaction (CRITICAL)
|
||||
|
||||
- `touch-target-size` - Minimum 44x44px touch targets
|
||||
- `hover-vs-tap` - Use click/tap for primary interactions
|
||||
- `loading-buttons` - Disable button during async operations
|
||||
- `error-feedback` - Clear error messages near problem
|
||||
- `cursor-pointer` - Add cursor-pointer to clickable elements
|
||||
|
||||
### 3. Performance (HIGH)
|
||||
|
||||
- `image-optimization` - Use WebP, srcset, lazy loading
|
||||
- `reduced-motion` - Check prefers-reduced-motion
|
||||
- `content-jumping` - Reserve space for async content
|
||||
|
||||
### 4. Layout & Responsive (HIGH)
|
||||
|
||||
- `viewport-meta` - width=device-width initial-scale=1
|
||||
- `readable-font-size` - Minimum 16px body text on mobile
|
||||
- `horizontal-scroll` - Ensure content fits viewport width
|
||||
- `z-index-management` - Define z-index scale (10, 20, 30, 50)
|
||||
|
||||
### 5. Typography & Color (MEDIUM)
|
||||
|
||||
- `line-height` - Use 1.5-1.75 for body text
|
||||
- `line-length` - Limit to 65-75 characters per line
|
||||
- `font-pairing` - Match heading/body font personalities
|
||||
|
||||
### 6. Animation (MEDIUM)
|
||||
|
||||
- `duration-timing` - Use 150-300ms for micro-interactions
|
||||
- `transform-performance` - Use transform/opacity, not width/height
|
||||
- `loading-states` - Skeleton screens or spinners
|
||||
|
||||
### 7. Style Selection (MEDIUM)
|
||||
|
||||
- `style-match` - Match style to product type
|
||||
- `consistency` - Use same style across all pages
|
||||
- `no-emoji-icons` - Use SVG icons, not emojis
|
||||
|
||||
### 8. Charts & Data (LOW)
|
||||
|
||||
- `chart-type` - Match chart type to data type
|
||||
- `color-guidance` - Use accessible color palettes
|
||||
- `data-table` - Provide table alternative for accessibility
|
||||
|
||||
## How to Use
|
||||
|
||||
Search specific domains using the CLI tool below.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Check if Python is installed:
|
||||
|
||||
```bash
|
||||
python3 --version || python --version
|
||||
```
|
||||
|
||||
If Python is not installed, install it based on user's OS:
|
||||
|
||||
**macOS:**
|
||||
```bash
|
||||
brew install python3
|
||||
```
|
||||
|
||||
**Ubuntu/Debian:**
|
||||
```bash
|
||||
sudo apt update && sudo apt install python3
|
||||
```
|
||||
|
||||
**Windows:**
|
||||
```powershell
|
||||
winget install Python.Python.3.12
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## How to Use This Skill
|
||||
|
||||
When user requests UI/UX work (design, build, create, implement, review, fix, improve), follow this workflow:
|
||||
|
||||
### Step 1: Analyze User Requirements
|
||||
|
||||
Extract key information from user request:
|
||||
- **Product type**: SaaS, e-commerce, portfolio, dashboard, landing page, etc.
|
||||
- **Style keywords**: minimal, playful, professional, elegant, dark mode, etc.
|
||||
- **Industry**: healthcare, fintech, gaming, education, etc.
|
||||
- **Stack**: React, Vue, Next.js, or default to `html-tailwind`
|
||||
|
||||
### Step 2: Generate Design System (REQUIRED)
|
||||
|
||||
**Always start with `--design-system`** to get comprehensive recommendations with reasoning:
|
||||
|
||||
```bash
|
||||
python3 skills/ui-ux-pro-max/scripts/search.py "<product_type> <industry> <keywords>" --design-system [-p "Project Name"]
|
||||
```
|
||||
|
||||
This command:
|
||||
1. Searches 5 domains in parallel (product, style, color, landing, typography)
|
||||
2. Applies reasoning rules from `ui-reasoning.csv` to select best matches
|
||||
3. Returns complete design system: pattern, style, colors, typography, effects
|
||||
4. Includes anti-patterns to avoid
|
||||
|
||||
**Example:**
|
||||
```bash
|
||||
python3 skills/ui-ux-pro-max/scripts/search.py "beauty spa wellness service" --design-system -p "Serenity Spa"
|
||||
```
|
||||
|
||||
### Step 2b: Persist Design System (Master + Overrides Pattern)
|
||||
|
||||
To save the design system for **hierarchical retrieval across sessions**, add `--persist`:
|
||||
|
||||
```bash
|
||||
python3 skills/ui-ux-pro-max/scripts/search.py "<query>" --design-system --persist -p "Project Name"
|
||||
```
|
||||
|
||||
This creates:
|
||||
- `design-system/MASTER.md` — Global Source of Truth with all design rules
|
||||
- `design-system/pages/` — Folder for page-specific overrides
|
||||
|
||||
**With page-specific override:**
|
||||
```bash
|
||||
python3 skills/ui-ux-pro-max/scripts/search.py "<query>" --design-system --persist -p "Project Name" --page "dashboard"
|
||||
```
|
||||
|
||||
This also creates:
|
||||
- `design-system/pages/dashboard.md` — Page-specific deviations from Master
|
||||
|
||||
**How hierarchical retrieval works:**
|
||||
1. When building a specific page (e.g., "Checkout"), first check `design-system/pages/checkout.md`
|
||||
2. If the page file exists, its rules **override** the Master file
|
||||
3. If not, use `design-system/MASTER.md` exclusively
|
||||
|
||||
**Context-aware retrieval prompt:**
|
||||
```
|
||||
I am building the [Page Name] page. Please read design-system/MASTER.md.
|
||||
Also check if design-system/pages/[page-name].md exists.
|
||||
If the page file exists, prioritize its rules.
|
||||
If not, use the Master rules exclusively.
|
||||
Now, generate the code...
|
||||
```
|
||||
|
||||
### Step 3: Supplement with Detailed Searches (as needed)
|
||||
|
||||
After getting the design system, use domain searches to get additional details:
|
||||
|
||||
```bash
|
||||
python3 skills/ui-ux-pro-max/scripts/search.py "<keyword>" --domain <domain> [-n <max_results>]
|
||||
```
|
||||
|
||||
**When to use detailed searches:**
|
||||
|
||||
| Need | Domain | Example |
|
||||
|------|--------|---------|
|
||||
| More style options | `style` | `--domain style "glassmorphism dark"` |
|
||||
| Chart recommendations | `chart` | `--domain chart "real-time dashboard"` |
|
||||
| UX best practices | `ux` | `--domain ux "animation accessibility"` |
|
||||
| Alternative fonts | `typography` | `--domain typography "elegant luxury"` |
|
||||
| Landing structure | `landing` | `--domain landing "hero social-proof"` |
|
||||
|
||||
### Step 4: Stack Guidelines (Default: html-tailwind)
|
||||
|
||||
Get implementation-specific best practices. If user doesn't specify a stack, **default to `html-tailwind`**.
|
||||
|
||||
```bash
|
||||
python3 skills/ui-ux-pro-max/scripts/search.py "<keyword>" --stack html-tailwind
|
||||
```
|
||||
|
||||
Available stacks: `html-tailwind`, `react`, `nextjs`, `vue`, `svelte`, `swiftui`, `react-native`, `flutter`, `shadcn`, `jetpack-compose`
|
||||
|
||||
---
|
||||
|
||||
## Search Reference
|
||||
|
||||
### Available Domains
|
||||
|
||||
| Domain | Use For | Example Keywords |
|
||||
|--------|---------|------------------|
|
||||
| `product` | Product type recommendations | SaaS, e-commerce, portfolio, healthcare, beauty, service |
|
||||
| `style` | UI styles, colors, effects | glassmorphism, minimalism, dark mode, brutalism |
|
||||
| `typography` | Font pairings, Google Fonts | elegant, playful, professional, modern |
|
||||
| `color` | Color palettes by product type | saas, ecommerce, healthcare, beauty, fintech, service |
|
||||
| `landing` | Page structure, CTA strategies | hero, hero-centric, testimonial, pricing, social-proof |
|
||||
| `chart` | Chart types, library recommendations | trend, comparison, timeline, funnel, pie |
|
||||
| `ux` | Best practices, anti-patterns | animation, accessibility, z-index, loading |
|
||||
| `react` | React/Next.js performance | waterfall, bundle, suspense, memo, rerender, cache |
|
||||
| `web` | Web interface guidelines | aria, focus, keyboard, semantic, virtualize |
|
||||
| `prompt` | AI prompts, CSS keywords | (style name) |
|
||||
|
||||
### Available Stacks
|
||||
|
||||
| Stack | Focus |
|
||||
|-------|-------|
|
||||
| `html-tailwind` | Tailwind utilities, responsive, a11y (DEFAULT) |
|
||||
| `react` | State, hooks, performance, patterns |
|
||||
| `nextjs` | SSR, routing, images, API routes |
|
||||
| `vue` | Composition API, Pinia, Vue Router |
|
||||
| `svelte` | Runes, stores, SvelteKit |
|
||||
| `swiftui` | Views, State, Navigation, Animation |
|
||||
| `react-native` | Components, Navigation, Lists |
|
||||
| `flutter` | Widgets, State, Layout, Theming |
|
||||
| `shadcn` | shadcn/ui components, theming, forms, patterns |
|
||||
| `jetpack-compose` | Composables, Modifiers, State Hoisting, Recomposition |
|
||||
|
||||
---
|
||||
|
||||
## Example Workflow
|
||||
|
||||
**User request:** "Làm landing page cho dịch vụ chăm sóc da chuyên nghiệp"
|
||||
|
||||
### Step 1: Analyze Requirements
|
||||
- Product type: Beauty/Spa service
|
||||
- Style keywords: elegant, professional, soft
|
||||
- Industry: Beauty/Wellness
|
||||
- Stack: html-tailwind (default)
|
||||
|
||||
### Step 2: Generate Design System (REQUIRED)
|
||||
|
||||
```bash
|
||||
python3 skills/ui-ux-pro-max/scripts/search.py "beauty spa wellness service elegant" --design-system -p "Serenity Spa"
|
||||
```
|
||||
|
||||
**Output:** Complete design system with pattern, style, colors, typography, effects, and anti-patterns.
|
||||
|
||||
### Step 3: Supplement with Detailed Searches (as needed)
|
||||
|
||||
```bash
|
||||
# Get UX guidelines for animation and accessibility
|
||||
python3 skills/ui-ux-pro-max/scripts/search.py "animation accessibility" --domain ux
|
||||
|
||||
# Get alternative typography options if needed
|
||||
python3 skills/ui-ux-pro-max/scripts/search.py "elegant luxury serif" --domain typography
|
||||
```
|
||||
|
||||
### Step 4: Stack Guidelines
|
||||
|
||||
```bash
|
||||
python3 skills/ui-ux-pro-max/scripts/search.py "layout responsive form" --stack html-tailwind
|
||||
```
|
||||
|
||||
**Then:** Synthesize design system + detailed searches and implement the design.
|
||||
|
||||
---
|
||||
|
||||
## Output Formats
|
||||
|
||||
The `--design-system` flag supports two output formats:
|
||||
|
||||
```bash
|
||||
# ASCII box (default) - best for terminal display
|
||||
python3 skills/ui-ux-pro-max/scripts/search.py "fintech crypto" --design-system
|
||||
|
||||
# Markdown - best for documentation
|
||||
python3 skills/ui-ux-pro-max/scripts/search.py "fintech crypto" --design-system -f markdown
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Tips for Better Results
|
||||
|
||||
1. **Be specific with keywords** - "healthcare SaaS dashboard" > "app"
|
||||
2. **Search multiple times** - Different keywords reveal different insights
|
||||
3. **Combine domains** - Style + Typography + Color = Complete design system
|
||||
4. **Always check UX** - Search "animation", "z-index", "accessibility" for common issues
|
||||
5. **Use stack flag** - Get implementation-specific best practices
|
||||
6. **Iterate** - If first search doesn't match, try different keywords
|
||||
|
||||
---
|
||||
|
||||
## Common Rules for Professional UI
|
||||
|
||||
These are frequently overlooked issues that make UI look unprofessional:
|
||||
|
||||
### Icons & Visual Elements
|
||||
|
||||
| Rule | Do | Don't |
|
||||
|------|----|----- |
|
||||
| **No emoji icons** | Use SVG icons (Heroicons, Lucide, Simple Icons) | Use emojis like 🎨 🚀 ⚙️ as UI icons |
|
||||
| **Stable hover states** | Use color/opacity transitions on hover | Use scale transforms that shift layout |
|
||||
| **Correct brand logos** | Research official SVG from Simple Icons | Guess or use incorrect logo paths |
|
||||
| **Consistent icon sizing** | Use fixed viewBox (24x24) with w-6 h-6 | Mix different icon sizes randomly |
|
||||
|
||||
### Interaction & Cursor
|
||||
|
||||
| Rule | Do | Don't |
|
||||
|------|----|----- |
|
||||
| **Cursor pointer** | Add `cursor-pointer` to all clickable/hoverable cards | Leave default cursor on interactive elements |
|
||||
| **Hover feedback** | Provide visual feedback (color, shadow, border) | No indication element is interactive |
|
||||
| **Smooth transitions** | Use `transition-colors duration-200` | Instant state changes or too slow (>500ms) |
|
||||
|
||||
### Light/Dark Mode Contrast
|
||||
|
||||
| Rule | Do | Don't |
|
||||
|------|----|----- |
|
||||
| **Glass card light mode** | Use `bg-white/80` or higher opacity | Use `bg-white/10` (too transparent) |
|
||||
| **Text contrast light** | Use `#0F172A` (slate-900) for text | Use `#94A3B8` (slate-400) for body text |
|
||||
| **Muted text light** | Use `#475569` (slate-600) minimum | Use gray-400 or lighter |
|
||||
| **Border visibility** | Use `border-gray-200` in light mode | Use `border-white/10` (invisible) |
|
||||
|
||||
### Layout & Spacing
|
||||
|
||||
| Rule | Do | Don't |
|
||||
|------|----|----- |
|
||||
| **Floating navbar** | Add `top-4 left-4 right-4` spacing | Stick navbar to `top-0 left-0 right-0` |
|
||||
| **Content padding** | Account for fixed navbar height | Let content hide behind fixed elements |
|
||||
| **Consistent max-width** | Use same `max-w-6xl` or `max-w-7xl` | Mix different container widths |
|
||||
|
||||
---
|
||||
|
||||
## Pre-Delivery Checklist
|
||||
|
||||
Before delivering UI code, verify these items:
|
||||
|
||||
### Visual Quality
|
||||
- [ ] No emojis used as icons (use SVG instead)
|
||||
- [ ] All icons from consistent icon set (Heroicons/Lucide)
|
||||
- [ ] Brand logos are correct (verified from Simple Icons)
|
||||
- [ ] Hover states don't cause layout shift
|
||||
- [ ] Use theme colors directly (bg-primary) not var() wrapper
|
||||
|
||||
### Interaction
|
||||
- [ ] All clickable elements have `cursor-pointer`
|
||||
- [ ] Hover states provide clear visual feedback
|
||||
- [ ] Transitions are smooth (150-300ms)
|
||||
- [ ] Focus states visible for keyboard navigation
|
||||
|
||||
### Light/Dark Mode
|
||||
- [ ] Light mode text has sufficient contrast (4.5:1 minimum)
|
||||
- [ ] Glass/transparent elements visible in light mode
|
||||
- [ ] Borders visible in both modes
|
||||
- [ ] Test both modes before delivery
|
||||
|
||||
### Layout
|
||||
- [ ] Floating elements have proper spacing from edges
|
||||
- [ ] No content hidden behind fixed navbars
|
||||
- [ ] Responsive at 375px, 768px, 1024px, 1440px
|
||||
- [ ] No horizontal scroll on mobile
|
||||
|
||||
### Accessibility
|
||||
- [ ] All images have alt text
|
||||
- [ ] Form inputs have labels
|
||||
- [ ] Color is not the only indicator
|
||||
- [ ] `prefers-reduced-motion` respected
|
||||
@@ -1 +0,0 @@
|
||||
../../../src/ui-ux-pro-max/data
|
||||
@@ -1 +0,0 @@
|
||||
../../../src/ui-ux-pro-max/scripts
|
||||
@@ -1,39 +0,0 @@
|
||||
---
|
||||
name: web-design-guidelines
|
||||
description: Review UI code for Web Interface Guidelines compliance. Use when asked to "review my UI", "check accessibility", "audit design", "review UX", or "check my site against best practices".
|
||||
metadata:
|
||||
author: vercel
|
||||
version: "1.0.0"
|
||||
argument-hint: <file-or-pattern>
|
||||
---
|
||||
|
||||
# Web Interface Guidelines
|
||||
|
||||
Review files for compliance with Web Interface Guidelines.
|
||||
|
||||
## How It Works
|
||||
|
||||
1. Fetch the latest guidelines from the source URL below
|
||||
2. Read the specified files (or prompt user for files/pattern)
|
||||
3. Check against all rules in the fetched guidelines
|
||||
4. Output findings in the terse `file:line` format
|
||||
|
||||
## Guidelines Source
|
||||
|
||||
Fetch fresh guidelines before each review:
|
||||
|
||||
```
|
||||
https://raw.githubusercontent.com/vercel-labs/web-interface-guidelines/main/command.md
|
||||
```
|
||||
|
||||
Use WebFetch to retrieve the latest rules. The fetched content contains all the rules and output format instructions.
|
||||
|
||||
## Usage
|
||||
|
||||
When a user provides a file or pattern argument:
|
||||
1. Fetch guidelines from the source URL above
|
||||
2. Read the specified files
|
||||
3. Apply all rules from the fetched guidelines
|
||||
4. Output findings using the format specified in the guidelines
|
||||
|
||||
If no files specified, ask the user which files to review.
|
||||
@@ -1,30 +0,0 @@
|
||||
© 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.
|
||||
@@ -1,292 +0,0 @@
|
||||
---
|
||||
name: xlsx
|
||||
description: "Use this skill any time a spreadsheet file is the primary input or output. This means any task where the user wants to: open, read, edit, or fix an existing .xlsx, .xlsm, .csv, or .tsv file (e.g., adding columns, computing formulas, formatting, charting, cleaning messy data); create a new spreadsheet from scratch or from other data sources; or convert between tabular file formats. Trigger especially when the user references a spreadsheet file by name or path — even casually (like \"the xlsx in my downloads\") — and wants something done to it or produced from it. Also trigger for cleaning or restructuring messy tabular data files (malformed rows, misplaced headers, junk data) into proper spreadsheets. The deliverable must be a spreadsheet file. Do NOT trigger when the primary deliverable is a Word document, HTML report, standalone Python script, database pipeline, or Google Sheets API integration, even if tabular data is involved."
|
||||
license: Proprietary. LICENSE.txt has complete terms
|
||||
---
|
||||
|
||||
# Requirements for Outputs
|
||||
|
||||
## All Excel files
|
||||
|
||||
### Professional Font
|
||||
- Use a consistent, professional font (e.g., Arial, Times New Roman) for all deliverables unless otherwise instructed by the user
|
||||
|
||||
### Zero Formula Errors
|
||||
- Every Excel model MUST be delivered with ZERO formula errors (#REF!, #DIV/0!, #VALUE!, #N/A, #NAME?)
|
||||
|
||||
### Preserve Existing Templates (when updating templates)
|
||||
- Study and EXACTLY match existing format, style, and conventions when modifying files
|
||||
- Never impose standardized formatting on files with established patterns
|
||||
- Existing template conventions ALWAYS override these guidelines
|
||||
|
||||
## Financial models
|
||||
|
||||
### Color Coding Standards
|
||||
Unless otherwise stated by the user or existing template
|
||||
|
||||
#### Industry-Standard Color Conventions
|
||||
- **Blue text (RGB: 0,0,255)**: Hardcoded inputs, and numbers users will change for scenarios
|
||||
- **Black text (RGB: 0,0,0)**: ALL formulas and calculations
|
||||
- **Green text (RGB: 0,128,0)**: Links pulling from other worksheets within same workbook
|
||||
- **Red text (RGB: 255,0,0)**: External links to other files
|
||||
- **Yellow background (RGB: 255,255,0)**: Key assumptions needing attention or cells that need to be updated
|
||||
|
||||
### Number Formatting Standards
|
||||
|
||||
#### Required Format Rules
|
||||
- **Years**: Format as text strings (e.g., "2024" not "2,024")
|
||||
- **Currency**: Use $#,##0 format; ALWAYS specify units in headers ("Revenue ($mm)")
|
||||
- **Zeros**: Use number formatting to make all zeros "-", including percentages (e.g., "$#,##0;($#,##0);-")
|
||||
- **Percentages**: Default to 0.0% format (one decimal)
|
||||
- **Multiples**: Format as 0.0x for valuation multiples (EV/EBITDA, P/E)
|
||||
- **Negative numbers**: Use parentheses (123) not minus -123
|
||||
|
||||
### Formula Construction Rules
|
||||
|
||||
#### Assumptions Placement
|
||||
- Place ALL assumptions (growth rates, margins, multiples, etc.) in separate assumption cells
|
||||
- Use cell references instead of hardcoded values in formulas
|
||||
- Example: Use =B5*(1+$B$6) instead of =B5*1.05
|
||||
|
||||
#### Formula Error Prevention
|
||||
- Verify all cell references are correct
|
||||
- Check for off-by-one errors in ranges
|
||||
- Ensure consistent formulas across all projection periods
|
||||
- Test with edge cases (zero values, negative numbers)
|
||||
- Verify no unintended circular references
|
||||
|
||||
#### Documentation Requirements for Hardcodes
|
||||
- Comment or in cells beside (if end of table). Format: "Source: [System/Document], [Date], [Specific Reference], [URL if applicable]"
|
||||
- Examples:
|
||||
- "Source: Company 10-K, FY2024, Page 45, Revenue Note, [SEC EDGAR URL]"
|
||||
- "Source: Company 10-Q, Q2 2025, Exhibit 99.1, [SEC EDGAR URL]"
|
||||
- "Source: Bloomberg Terminal, 8/15/2025, AAPL US Equity"
|
||||
- "Source: FactSet, 8/20/2025, Consensus Estimates Screen"
|
||||
|
||||
# XLSX creation, editing, and analysis
|
||||
|
||||
## Overview
|
||||
|
||||
A user may ask you to create, edit, or analyze the contents of an .xlsx file. You have different tools and workflows available for different tasks.
|
||||
|
||||
## Important Requirements
|
||||
|
||||
**LibreOffice Required for Formula Recalculation**: You can assume LibreOffice is installed for recalculating formula values using the `scripts/recalc.py` script. The script automatically configures LibreOffice on first run, including in sandboxed environments where Unix sockets are restricted (handled by `scripts/office/soffice.py`)
|
||||
|
||||
## Reading and analyzing data
|
||||
|
||||
### Data analysis with pandas
|
||||
For data analysis, visualization, and basic operations, use **pandas** which provides powerful data manipulation capabilities:
|
||||
|
||||
```python
|
||||
import pandas as pd
|
||||
|
||||
# Read Excel
|
||||
df = pd.read_excel('file.xlsx') # Default: first sheet
|
||||
all_sheets = pd.read_excel('file.xlsx', sheet_name=None) # All sheets as dict
|
||||
|
||||
# Analyze
|
||||
df.head() # Preview data
|
||||
df.info() # Column info
|
||||
df.describe() # Statistics
|
||||
|
||||
# Write Excel
|
||||
df.to_excel('output.xlsx', index=False)
|
||||
```
|
||||
|
||||
## Excel File Workflows
|
||||
|
||||
## CRITICAL: Use Formulas, Not Hardcoded Values
|
||||
|
||||
**Always use Excel formulas instead of calculating values in Python and hardcoding them.** This ensures the spreadsheet remains dynamic and updateable.
|
||||
|
||||
### ❌ WRONG - Hardcoding Calculated Values
|
||||
```python
|
||||
# Bad: Calculating in Python and hardcoding result
|
||||
total = df['Sales'].sum()
|
||||
sheet['B10'] = total # Hardcodes 5000
|
||||
|
||||
# Bad: Computing growth rate in Python
|
||||
growth = (df.iloc[-1]['Revenue'] - df.iloc[0]['Revenue']) / df.iloc[0]['Revenue']
|
||||
sheet['C5'] = growth # Hardcodes 0.15
|
||||
|
||||
# Bad: Python calculation for average
|
||||
avg = sum(values) / len(values)
|
||||
sheet['D20'] = avg # Hardcodes 42.5
|
||||
```
|
||||
|
||||
### ✅ CORRECT - Using Excel Formulas
|
||||
```python
|
||||
# Good: Let Excel calculate the sum
|
||||
sheet['B10'] = '=SUM(B2:B9)'
|
||||
|
||||
# Good: Growth rate as Excel formula
|
||||
sheet['C5'] = '=(C4-C2)/C2'
|
||||
|
||||
# Good: Average using Excel function
|
||||
sheet['D20'] = '=AVERAGE(D2:D19)'
|
||||
```
|
||||
|
||||
This applies to ALL calculations - totals, percentages, ratios, differences, etc. The spreadsheet should be able to recalculate when source data changes.
|
||||
|
||||
## Common Workflow
|
||||
1. **Choose tool**: pandas for data, openpyxl for formulas/formatting
|
||||
2. **Create/Load**: Create new workbook or load existing file
|
||||
3. **Modify**: Add/edit data, formulas, and formatting
|
||||
4. **Save**: Write to file
|
||||
5. **Recalculate formulas (MANDATORY IF USING FORMULAS)**: Use the scripts/recalc.py script
|
||||
```bash
|
||||
python scripts/recalc.py output.xlsx
|
||||
```
|
||||
6. **Verify and fix any errors**:
|
||||
- The script returns JSON with error details
|
||||
- If `status` is `errors_found`, check `error_summary` for specific error types and locations
|
||||
- Fix the identified errors and recalculate again
|
||||
- Common errors to fix:
|
||||
- `#REF!`: Invalid cell references
|
||||
- `#DIV/0!`: Division by zero
|
||||
- `#VALUE!`: Wrong data type in formula
|
||||
- `#NAME?`: Unrecognized formula name
|
||||
|
||||
### Creating new Excel files
|
||||
|
||||
```python
|
||||
# Using openpyxl for formulas and formatting
|
||||
from openpyxl import Workbook
|
||||
from openpyxl.styles import Font, PatternFill, Alignment
|
||||
|
||||
wb = Workbook()
|
||||
sheet = wb.active
|
||||
|
||||
# Add data
|
||||
sheet['A1'] = 'Hello'
|
||||
sheet['B1'] = 'World'
|
||||
sheet.append(['Row', 'of', 'data'])
|
||||
|
||||
# Add formula
|
||||
sheet['B2'] = '=SUM(A1:A10)'
|
||||
|
||||
# Formatting
|
||||
sheet['A1'].font = Font(bold=True, color='FF0000')
|
||||
sheet['A1'].fill = PatternFill('solid', start_color='FFFF00')
|
||||
sheet['A1'].alignment = Alignment(horizontal='center')
|
||||
|
||||
# Column width
|
||||
sheet.column_dimensions['A'].width = 20
|
||||
|
||||
wb.save('output.xlsx')
|
||||
```
|
||||
|
||||
### Editing existing Excel files
|
||||
|
||||
```python
|
||||
# Using openpyxl to preserve formulas and formatting
|
||||
from openpyxl import load_workbook
|
||||
|
||||
# Load existing file
|
||||
wb = load_workbook('existing.xlsx')
|
||||
sheet = wb.active # or wb['SheetName'] for specific sheet
|
||||
|
||||
# Working with multiple sheets
|
||||
for sheet_name in wb.sheetnames:
|
||||
sheet = wb[sheet_name]
|
||||
print(f"Sheet: {sheet_name}")
|
||||
|
||||
# Modify cells
|
||||
sheet['A1'] = 'New Value'
|
||||
sheet.insert_rows(2) # Insert row at position 2
|
||||
sheet.delete_cols(3) # Delete column 3
|
||||
|
||||
# Add new sheet
|
||||
new_sheet = wb.create_sheet('NewSheet')
|
||||
new_sheet['A1'] = 'Data'
|
||||
|
||||
wb.save('modified.xlsx')
|
||||
```
|
||||
|
||||
## Recalculating formulas
|
||||
|
||||
Excel files created or modified by openpyxl contain formulas as strings but not calculated values. Use the provided `scripts/recalc.py` script to recalculate formulas:
|
||||
|
||||
```bash
|
||||
python scripts/recalc.py <excel_file> [timeout_seconds]
|
||||
```
|
||||
|
||||
Example:
|
||||
```bash
|
||||
python scripts/recalc.py output.xlsx 30
|
||||
```
|
||||
|
||||
The script:
|
||||
- Automatically sets up LibreOffice macro on first run
|
||||
- Recalculates all formulas in all sheets
|
||||
- Scans ALL cells for Excel errors (#REF!, #DIV/0!, etc.)
|
||||
- Returns JSON with detailed error locations and counts
|
||||
- Works on both Linux and macOS
|
||||
|
||||
## Formula Verification Checklist
|
||||
|
||||
Quick checks to ensure formulas work correctly:
|
||||
|
||||
### Essential Verification
|
||||
- [ ] **Test 2-3 sample references**: Verify they pull correct values before building full model
|
||||
- [ ] **Column mapping**: Confirm Excel columns match (e.g., column 64 = BL, not BK)
|
||||
- [ ] **Row offset**: Remember Excel rows are 1-indexed (DataFrame row 5 = Excel row 6)
|
||||
|
||||
### Common Pitfalls
|
||||
- [ ] **NaN handling**: Check for null values with `pd.notna()`
|
||||
- [ ] **Far-right columns**: FY data often in columns 50+
|
||||
- [ ] **Multiple matches**: Search all occurrences, not just first
|
||||
- [ ] **Division by zero**: Check denominators before using `/` in formulas (#DIV/0!)
|
||||
- [ ] **Wrong references**: Verify all cell references point to intended cells (#REF!)
|
||||
- [ ] **Cross-sheet references**: Use correct format (Sheet1!A1) for linking sheets
|
||||
|
||||
### Formula Testing Strategy
|
||||
- [ ] **Start small**: Test formulas on 2-3 cells before applying broadly
|
||||
- [ ] **Verify dependencies**: Check all cells referenced in formulas exist
|
||||
- [ ] **Test edge cases**: Include zero, negative, and very large values
|
||||
|
||||
### Interpreting scripts/recalc.py Output
|
||||
The script returns JSON with error details:
|
||||
```json
|
||||
{
|
||||
"status": "success", // or "errors_found"
|
||||
"total_errors": 0, // Total error count
|
||||
"total_formulas": 42, // Number of formulas in file
|
||||
"error_summary": { // Only present if errors found
|
||||
"#REF!": {
|
||||
"count": 2,
|
||||
"locations": ["Sheet1!B5", "Sheet1!C10"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Library Selection
|
||||
- **pandas**: Best for data analysis, bulk operations, and simple data export
|
||||
- **openpyxl**: Best for complex formatting, formulas, and Excel-specific features
|
||||
|
||||
### Working with openpyxl
|
||||
- Cell indices are 1-based (row=1, column=1 refers to cell A1)
|
||||
- Use `data_only=True` to read calculated values: `load_workbook('file.xlsx', data_only=True)`
|
||||
- **Warning**: If opened with `data_only=True` and saved, formulas are replaced with values and permanently lost
|
||||
- For large files: Use `read_only=True` for reading or `write_only=True` for writing
|
||||
- Formulas are preserved but not evaluated - use scripts/recalc.py to update values
|
||||
|
||||
### Working with pandas
|
||||
- Specify data types to avoid inference issues: `pd.read_excel('file.xlsx', dtype={'id': str})`
|
||||
- For large files, read specific columns: `pd.read_excel('file.xlsx', usecols=['A', 'C', 'E'])`
|
||||
- Handle dates properly: `pd.read_excel('file.xlsx', parse_dates=['date_column'])`
|
||||
|
||||
## Code Style Guidelines
|
||||
**IMPORTANT**: When generating Python code for Excel operations:
|
||||
- Write minimal, concise Python code without unnecessary comments
|
||||
- Avoid verbose variable names and redundant operations
|
||||
- Avoid unnecessary print statements
|
||||
|
||||
**For Excel files themselves**:
|
||||
- Add comments to cells with complex formulas or important assumptions
|
||||
- Document data sources for hardcoded values
|
||||
- Include notes for key calculations and model sections
|
||||
@@ -1,199 +0,0 @@
|
||||
"""Merge adjacent runs with identical formatting in DOCX.
|
||||
|
||||
Merges adjacent <w:r> elements that have identical <w:rPr> properties.
|
||||
Works on runs in paragraphs and inside tracked changes (<w:ins>, <w:del>).
|
||||
|
||||
Also:
|
||||
- Removes rsid attributes from runs (revision metadata that doesn't affect rendering)
|
||||
- Removes proofErr elements (spell/grammar markers that block merging)
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import defusedxml.minidom
|
||||
|
||||
|
||||
def merge_runs(input_dir: str) -> tuple[int, str]:
|
||||
doc_xml = Path(input_dir) / "word" / "document.xml"
|
||||
|
||||
if not doc_xml.exists():
|
||||
return 0, f"Error: {doc_xml} not found"
|
||||
|
||||
try:
|
||||
dom = defusedxml.minidom.parseString(doc_xml.read_text(encoding="utf-8"))
|
||||
root = dom.documentElement
|
||||
|
||||
_remove_elements(root, "proofErr")
|
||||
_strip_run_rsid_attrs(root)
|
||||
|
||||
containers = {run.parentNode for run in _find_elements(root, "r")}
|
||||
|
||||
merge_count = 0
|
||||
for container in containers:
|
||||
merge_count += _merge_runs_in(container)
|
||||
|
||||
doc_xml.write_bytes(dom.toxml(encoding="UTF-8"))
|
||||
return merge_count, f"Merged {merge_count} runs"
|
||||
|
||||
except Exception as e:
|
||||
return 0, f"Error: {e}"
|
||||
|
||||
|
||||
|
||||
|
||||
def _find_elements(root, tag: str) -> list:
|
||||
results = []
|
||||
|
||||
def traverse(node):
|
||||
if node.nodeType == node.ELEMENT_NODE:
|
||||
name = node.localName or node.tagName
|
||||
if name == tag or name.endswith(f":{tag}"):
|
||||
results.append(node)
|
||||
for child in node.childNodes:
|
||||
traverse(child)
|
||||
|
||||
traverse(root)
|
||||
return results
|
||||
|
||||
|
||||
def _get_child(parent, tag: str):
|
||||
for child in parent.childNodes:
|
||||
if child.nodeType == child.ELEMENT_NODE:
|
||||
name = child.localName or child.tagName
|
||||
if name == tag or name.endswith(f":{tag}"):
|
||||
return child
|
||||
return None
|
||||
|
||||
|
||||
def _get_children(parent, tag: str) -> list:
|
||||
results = []
|
||||
for child in parent.childNodes:
|
||||
if child.nodeType == child.ELEMENT_NODE:
|
||||
name = child.localName or child.tagName
|
||||
if name == tag or name.endswith(f":{tag}"):
|
||||
results.append(child)
|
||||
return results
|
||||
|
||||
|
||||
def _is_adjacent(elem1, elem2) -> bool:
|
||||
node = elem1.nextSibling
|
||||
while node:
|
||||
if node == elem2:
|
||||
return True
|
||||
if node.nodeType == node.ELEMENT_NODE:
|
||||
return False
|
||||
if node.nodeType == node.TEXT_NODE and node.data.strip():
|
||||
return False
|
||||
node = node.nextSibling
|
||||
return False
|
||||
|
||||
|
||||
|
||||
|
||||
def _remove_elements(root, tag: str):
|
||||
for elem in _find_elements(root, tag):
|
||||
if elem.parentNode:
|
||||
elem.parentNode.removeChild(elem)
|
||||
|
||||
|
||||
def _strip_run_rsid_attrs(root):
|
||||
for run in _find_elements(root, "r"):
|
||||
for attr in list(run.attributes.values()):
|
||||
if "rsid" in attr.name.lower():
|
||||
run.removeAttribute(attr.name)
|
||||
|
||||
|
||||
|
||||
|
||||
def _merge_runs_in(container) -> int:
|
||||
merge_count = 0
|
||||
run = _first_child_run(container)
|
||||
|
||||
while run:
|
||||
while True:
|
||||
next_elem = _next_element_sibling(run)
|
||||
if next_elem and _is_run(next_elem) and _can_merge(run, next_elem):
|
||||
_merge_run_content(run, next_elem)
|
||||
container.removeChild(next_elem)
|
||||
merge_count += 1
|
||||
else:
|
||||
break
|
||||
|
||||
_consolidate_text(run)
|
||||
run = _next_sibling_run(run)
|
||||
|
||||
return merge_count
|
||||
|
||||
|
||||
def _first_child_run(container):
|
||||
for child in container.childNodes:
|
||||
if child.nodeType == child.ELEMENT_NODE and _is_run(child):
|
||||
return child
|
||||
return None
|
||||
|
||||
|
||||
def _next_element_sibling(node):
|
||||
sibling = node.nextSibling
|
||||
while sibling:
|
||||
if sibling.nodeType == sibling.ELEMENT_NODE:
|
||||
return sibling
|
||||
sibling = sibling.nextSibling
|
||||
return None
|
||||
|
||||
|
||||
def _next_sibling_run(node):
|
||||
sibling = node.nextSibling
|
||||
while sibling:
|
||||
if sibling.nodeType == sibling.ELEMENT_NODE:
|
||||
if _is_run(sibling):
|
||||
return sibling
|
||||
sibling = sibling.nextSibling
|
||||
return None
|
||||
|
||||
|
||||
def _is_run(node) -> bool:
|
||||
name = node.localName or node.tagName
|
||||
return name == "r" or name.endswith(":r")
|
||||
|
||||
|
||||
def _can_merge(run1, run2) -> bool:
|
||||
rpr1 = _get_child(run1, "rPr")
|
||||
rpr2 = _get_child(run2, "rPr")
|
||||
|
||||
if (rpr1 is None) != (rpr2 is None):
|
||||
return False
|
||||
if rpr1 is None:
|
||||
return True
|
||||
return rpr1.toxml() == rpr2.toxml()
|
||||
|
||||
|
||||
def _merge_run_content(target, source):
|
||||
for child in list(source.childNodes):
|
||||
if child.nodeType == child.ELEMENT_NODE:
|
||||
name = child.localName or child.tagName
|
||||
if name != "rPr" and not name.endswith(":rPr"):
|
||||
target.appendChild(child)
|
||||
|
||||
|
||||
def _consolidate_text(run):
|
||||
t_elements = _get_children(run, "t")
|
||||
|
||||
for i in range(len(t_elements) - 1, 0, -1):
|
||||
curr, prev = t_elements[i], t_elements[i - 1]
|
||||
|
||||
if _is_adjacent(prev, curr):
|
||||
prev_text = prev.firstChild.data if prev.firstChild else ""
|
||||
curr_text = curr.firstChild.data if curr.firstChild else ""
|
||||
merged = prev_text + curr_text
|
||||
|
||||
if prev.firstChild:
|
||||
prev.firstChild.data = merged
|
||||
else:
|
||||
prev.appendChild(run.ownerDocument.createTextNode(merged))
|
||||
|
||||
if merged.startswith(" ") or merged.endswith(" "):
|
||||
prev.setAttribute("xml:space", "preserve")
|
||||
elif prev.hasAttribute("xml:space"):
|
||||
prev.removeAttribute("xml:space")
|
||||
|
||||
run.removeChild(curr)
|
||||
@@ -1,197 +0,0 @@
|
||||
"""Simplify tracked changes by merging adjacent w:ins or w:del elements.
|
||||
|
||||
Merges adjacent <w:ins> elements from the same author into a single element.
|
||||
Same for <w:del> elements. This makes heavily-redlined documents easier to
|
||||
work with by reducing the number of tracked change wrappers.
|
||||
|
||||
Rules:
|
||||
- Only merges w:ins with w:ins, w:del with w:del (same element type)
|
||||
- Only merges if same author (ignores timestamp differences)
|
||||
- Only merges if truly adjacent (only whitespace between them)
|
||||
"""
|
||||
|
||||
import xml.etree.ElementTree as ET
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
import defusedxml.minidom
|
||||
|
||||
WORD_NS = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
|
||||
|
||||
|
||||
def simplify_redlines(input_dir: str) -> tuple[int, str]:
|
||||
doc_xml = Path(input_dir) / "word" / "document.xml"
|
||||
|
||||
if not doc_xml.exists():
|
||||
return 0, f"Error: {doc_xml} not found"
|
||||
|
||||
try:
|
||||
dom = defusedxml.minidom.parseString(doc_xml.read_text(encoding="utf-8"))
|
||||
root = dom.documentElement
|
||||
|
||||
merge_count = 0
|
||||
|
||||
containers = _find_elements(root, "p") + _find_elements(root, "tc")
|
||||
|
||||
for container in containers:
|
||||
merge_count += _merge_tracked_changes_in(container, "ins")
|
||||
merge_count += _merge_tracked_changes_in(container, "del")
|
||||
|
||||
doc_xml.write_bytes(dom.toxml(encoding="UTF-8"))
|
||||
return merge_count, f"Simplified {merge_count} tracked changes"
|
||||
|
||||
except Exception as e:
|
||||
return 0, f"Error: {e}"
|
||||
|
||||
|
||||
def _merge_tracked_changes_in(container, tag: str) -> int:
|
||||
merge_count = 0
|
||||
|
||||
tracked = [
|
||||
child
|
||||
for child in container.childNodes
|
||||
if child.nodeType == child.ELEMENT_NODE and _is_element(child, tag)
|
||||
]
|
||||
|
||||
if len(tracked) < 2:
|
||||
return 0
|
||||
|
||||
i = 0
|
||||
while i < len(tracked) - 1:
|
||||
curr = tracked[i]
|
||||
next_elem = tracked[i + 1]
|
||||
|
||||
if _can_merge_tracked(curr, next_elem):
|
||||
_merge_tracked_content(curr, next_elem)
|
||||
container.removeChild(next_elem)
|
||||
tracked.pop(i + 1)
|
||||
merge_count += 1
|
||||
else:
|
||||
i += 1
|
||||
|
||||
return merge_count
|
||||
|
||||
|
||||
def _is_element(node, tag: str) -> bool:
|
||||
name = node.localName or node.tagName
|
||||
return name == tag or name.endswith(f":{tag}")
|
||||
|
||||
|
||||
def _get_author(elem) -> str:
|
||||
author = elem.getAttribute("w:author")
|
||||
if not author:
|
||||
for attr in elem.attributes.values():
|
||||
if attr.localName == "author" or attr.name.endswith(":author"):
|
||||
return attr.value
|
||||
return author
|
||||
|
||||
|
||||
def _can_merge_tracked(elem1, elem2) -> bool:
|
||||
if _get_author(elem1) != _get_author(elem2):
|
||||
return False
|
||||
|
||||
node = elem1.nextSibling
|
||||
while node and node != elem2:
|
||||
if node.nodeType == node.ELEMENT_NODE:
|
||||
return False
|
||||
if node.nodeType == node.TEXT_NODE and node.data.strip():
|
||||
return False
|
||||
node = node.nextSibling
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def _merge_tracked_content(target, source):
|
||||
while source.firstChild:
|
||||
child = source.firstChild
|
||||
source.removeChild(child)
|
||||
target.appendChild(child)
|
||||
|
||||
|
||||
def _find_elements(root, tag: str) -> list:
|
||||
results = []
|
||||
|
||||
def traverse(node):
|
||||
if node.nodeType == node.ELEMENT_NODE:
|
||||
name = node.localName or node.tagName
|
||||
if name == tag or name.endswith(f":{tag}"):
|
||||
results.append(node)
|
||||
for child in node.childNodes:
|
||||
traverse(child)
|
||||
|
||||
traverse(root)
|
||||
return results
|
||||
|
||||
|
||||
def get_tracked_change_authors(doc_xml_path: Path) -> dict[str, int]:
|
||||
if not doc_xml_path.exists():
|
||||
return {}
|
||||
|
||||
try:
|
||||
tree = ET.parse(doc_xml_path)
|
||||
root = tree.getroot()
|
||||
except ET.ParseError:
|
||||
return {}
|
||||
|
||||
namespaces = {"w": WORD_NS}
|
||||
author_attr = f"{{{WORD_NS}}}author"
|
||||
|
||||
authors: dict[str, int] = {}
|
||||
for tag in ["ins", "del"]:
|
||||
for elem in root.findall(f".//w:{tag}", namespaces):
|
||||
author = elem.get(author_attr)
|
||||
if author:
|
||||
authors[author] = authors.get(author, 0) + 1
|
||||
|
||||
return authors
|
||||
|
||||
|
||||
def _get_authors_from_docx(docx_path: Path) -> dict[str, int]:
|
||||
try:
|
||||
with zipfile.ZipFile(docx_path, "r") as zf:
|
||||
if "word/document.xml" not in zf.namelist():
|
||||
return {}
|
||||
with zf.open("word/document.xml") as f:
|
||||
tree = ET.parse(f)
|
||||
root = tree.getroot()
|
||||
|
||||
namespaces = {"w": WORD_NS}
|
||||
author_attr = f"{{{WORD_NS}}}author"
|
||||
|
||||
authors: dict[str, int] = {}
|
||||
for tag in ["ins", "del"]:
|
||||
for elem in root.findall(f".//w:{tag}", namespaces):
|
||||
author = elem.get(author_attr)
|
||||
if author:
|
||||
authors[author] = authors.get(author, 0) + 1
|
||||
return authors
|
||||
except (zipfile.BadZipFile, ET.ParseError):
|
||||
return {}
|
||||
|
||||
|
||||
def infer_author(modified_dir: Path, original_docx: Path, default: str = "Claude") -> str:
|
||||
modified_xml = modified_dir / "word" / "document.xml"
|
||||
modified_authors = get_tracked_change_authors(modified_xml)
|
||||
|
||||
if not modified_authors:
|
||||
return default
|
||||
|
||||
original_authors = _get_authors_from_docx(original_docx)
|
||||
|
||||
new_changes: dict[str, int] = {}
|
||||
for author, count in modified_authors.items():
|
||||
original_count = original_authors.get(author, 0)
|
||||
diff = count - original_count
|
||||
if diff > 0:
|
||||
new_changes[author] = diff
|
||||
|
||||
if not new_changes:
|
||||
return default
|
||||
|
||||
if len(new_changes) == 1:
|
||||
return next(iter(new_changes))
|
||||
|
||||
raise ValueError(
|
||||
f"Multiple authors added new changes: {new_changes}. "
|
||||
"Cannot infer which author to validate."
|
||||
)
|
||||
@@ -1,159 +0,0 @@
|
||||
"""Pack a directory into a DOCX, PPTX, or XLSX file.
|
||||
|
||||
Validates with auto-repair, condenses XML formatting, and creates the Office file.
|
||||
|
||||
Usage:
|
||||
python pack.py <input_directory> <output_file> [--original <file>] [--validate true|false]
|
||||
|
||||
Examples:
|
||||
python pack.py unpacked/ output.docx --original input.docx
|
||||
python pack.py unpacked/ output.pptx --validate false
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
import shutil
|
||||
import tempfile
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
import defusedxml.minidom
|
||||
|
||||
from validators import DOCXSchemaValidator, PPTXSchemaValidator, RedliningValidator
|
||||
|
||||
def pack(
|
||||
input_directory: str,
|
||||
output_file: str,
|
||||
original_file: str | None = None,
|
||||
validate: bool = True,
|
||||
infer_author_func=None,
|
||||
) -> tuple[None, str]:
|
||||
input_dir = Path(input_directory)
|
||||
output_path = Path(output_file)
|
||||
suffix = output_path.suffix.lower()
|
||||
|
||||
if not input_dir.is_dir():
|
||||
return None, f"Error: {input_dir} is not a directory"
|
||||
|
||||
if suffix not in {".docx", ".pptx", ".xlsx"}:
|
||||
return None, f"Error: {output_file} must be a .docx, .pptx, or .xlsx file"
|
||||
|
||||
if validate and original_file:
|
||||
original_path = Path(original_file)
|
||||
if original_path.exists():
|
||||
success, output = _run_validation(
|
||||
input_dir, original_path, suffix, infer_author_func
|
||||
)
|
||||
if output:
|
||||
print(output)
|
||||
if not success:
|
||||
return None, f"Error: Validation failed for {input_dir}"
|
||||
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
temp_content_dir = Path(temp_dir) / "content"
|
||||
shutil.copytree(input_dir, temp_content_dir)
|
||||
|
||||
for pattern in ["*.xml", "*.rels"]:
|
||||
for xml_file in temp_content_dir.rglob(pattern):
|
||||
_condense_xml(xml_file)
|
||||
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with zipfile.ZipFile(output_path, "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))
|
||||
|
||||
return None, f"Successfully packed {input_dir} to {output_file}"
|
||||
|
||||
|
||||
def _run_validation(
|
||||
unpacked_dir: Path,
|
||||
original_file: Path,
|
||||
suffix: str,
|
||||
infer_author_func=None,
|
||||
) -> tuple[bool, str | None]:
|
||||
output_lines = []
|
||||
validators = []
|
||||
|
||||
if suffix == ".docx":
|
||||
author = "Claude"
|
||||
if infer_author_func:
|
||||
try:
|
||||
author = infer_author_func(unpacked_dir, original_file)
|
||||
except ValueError as e:
|
||||
print(f"Warning: {e} Using default author 'Claude'.", file=sys.stderr)
|
||||
|
||||
validators = [
|
||||
DOCXSchemaValidator(unpacked_dir, original_file),
|
||||
RedliningValidator(unpacked_dir, original_file, author=author),
|
||||
]
|
||||
elif suffix == ".pptx":
|
||||
validators = [PPTXSchemaValidator(unpacked_dir, original_file)]
|
||||
|
||||
if not validators:
|
||||
return True, None
|
||||
|
||||
total_repairs = sum(v.repair() for v in validators)
|
||||
if total_repairs:
|
||||
output_lines.append(f"Auto-repaired {total_repairs} issue(s)")
|
||||
|
||||
success = all(v.validate() for v in validators)
|
||||
|
||||
if success:
|
||||
output_lines.append("All validations PASSED!")
|
||||
|
||||
return success, "\n".join(output_lines) if output_lines else None
|
||||
|
||||
|
||||
def _condense_xml(xml_file: Path) -> None:
|
||||
try:
|
||||
with open(xml_file, encoding="utf-8") as f:
|
||||
dom = defusedxml.minidom.parse(f)
|
||||
|
||||
for element in dom.getElementsByTagName("*"):
|
||||
if element.tagName.endswith(":t"):
|
||||
continue
|
||||
|
||||
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)
|
||||
|
||||
xml_file.write_bytes(dom.toxml(encoding="UTF-8"))
|
||||
except Exception as e:
|
||||
print(f"ERROR: Failed to parse {xml_file.name}: {e}", file=sys.stderr)
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Pack a directory into a DOCX, PPTX, or XLSX 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(
|
||||
"--original",
|
||||
help="Original file for validation comparison",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--validate",
|
||||
type=lambda x: x.lower() == "true",
|
||||
default=True,
|
||||
metavar="true|false",
|
||||
help="Run validation with auto-repair (default: true)",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
_, message = pack(
|
||||
args.input_directory,
|
||||
args.output_file,
|
||||
original_file=args.original,
|
||||
validate=args.validate,
|
||||
)
|
||||
print(message)
|
||||
|
||||
if "Error" in message:
|
||||
sys.exit(1)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,146 +0,0 @@
|
||||
<?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>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,11 +0,0 @@
|
||||
<?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>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,23 +0,0 @@
|
||||
<?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>
|
||||
@@ -1,185 +0,0 @@
|
||||
<?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>
|
||||
@@ -1,287 +0,0 @@
|
||||
<?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>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,28 +0,0 @@
|
||||
<?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>
|
||||
@@ -1,144 +0,0 @@
|
||||
<?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>
|
||||
@@ -1,174 +0,0 @@
|
||||
<?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>
|
||||
@@ -1,25 +0,0 @@
|
||||
<?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>
|
||||
@@ -1,18 +0,0 @@
|
||||
<?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>
|
||||
@@ -1,59 +0,0 @@
|
||||
<?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>
|
||||
@@ -1,56 +0,0 @@
|
||||
<?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>
|
||||
@@ -1,195 +0,0 @@
|
||||
<?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>
|
||||
@@ -1,582 +0,0 @@
|
||||
<?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>
|
||||
@@ -1,25 +0,0 @@
|
||||
<?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>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,570 +0,0 @@
|
||||
<?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>
|
||||
@@ -1,509 +0,0 @@
|
||||
<?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>
|
||||
@@ -1,12 +0,0 @@
|
||||
<?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>
|
||||
@@ -1,108 +0,0 @@
|
||||
<?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>
|
||||
@@ -1,96 +0,0 @@
|
||||
<?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>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,116 +0,0 @@
|
||||
<?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>
|
||||
@@ -1,42 +0,0 @@
|
||||
<?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>
|
||||
@@ -1,50 +0,0 @@
|
||||
<?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>
|
||||
@@ -1,49 +0,0 @@
|
||||
<?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>
|
||||
@@ -1,33 +0,0 @@
|
||||
<?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>
|
||||
@@ -1,75 +0,0 @@
|
||||
<?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>
|
||||
@@ -1,560 +0,0 @@
|
||||
<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>
|
||||
@@ -1,67 +0,0 @@
|
||||
<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>
|
||||
@@ -1,14 +0,0 @@
|
||||
<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>
|
||||
@@ -1,20 +0,0 @@
|
||||
<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>
|
||||
@@ -1,13 +0,0 @@
|
||||
<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>
|
||||
@@ -1,4 +0,0 @@
|
||||
<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>
|
||||
@@ -1,8 +0,0 @@
|
||||
<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>
|
||||
@@ -1,183 +0,0 @@
|
||||
"""
|
||||
Helper for running LibreOffice (soffice) in environments where AF_UNIX
|
||||
sockets may be blocked (e.g., sandboxed VMs). Detects the restriction
|
||||
at runtime and applies an LD_PRELOAD shim if needed.
|
||||
|
||||
Usage:
|
||||
from office.soffice import run_soffice, get_soffice_env
|
||||
|
||||
# Option 1 – run soffice directly
|
||||
result = run_soffice(["--headless", "--convert-to", "pdf", "input.docx"])
|
||||
|
||||
# Option 2 – get env dict for your own subprocess calls
|
||||
env = get_soffice_env()
|
||||
subprocess.run(["soffice", ...], env=env)
|
||||
"""
|
||||
|
||||
import os
|
||||
import socket
|
||||
import subprocess
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def get_soffice_env() -> dict:
|
||||
env = os.environ.copy()
|
||||
env["SAL_USE_VCLPLUGIN"] = "svp"
|
||||
|
||||
if _needs_shim():
|
||||
shim = _ensure_shim()
|
||||
env["LD_PRELOAD"] = str(shim)
|
||||
|
||||
return env
|
||||
|
||||
|
||||
def run_soffice(args: list[str], **kwargs) -> subprocess.CompletedProcess:
|
||||
env = get_soffice_env()
|
||||
return subprocess.run(["soffice"] + args, env=env, **kwargs)
|
||||
|
||||
|
||||
|
||||
_SHIM_SO = Path(tempfile.gettempdir()) / "lo_socket_shim.so"
|
||||
|
||||
|
||||
def _needs_shim() -> bool:
|
||||
try:
|
||||
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
s.close()
|
||||
return False
|
||||
except OSError:
|
||||
return True
|
||||
|
||||
|
||||
def _ensure_shim() -> Path:
|
||||
if _SHIM_SO.exists():
|
||||
return _SHIM_SO
|
||||
|
||||
src = Path(tempfile.gettempdir()) / "lo_socket_shim.c"
|
||||
src.write_text(_SHIM_SOURCE)
|
||||
subprocess.run(
|
||||
["gcc", "-shared", "-fPIC", "-o", str(_SHIM_SO), str(src), "-ldl"],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
)
|
||||
src.unlink()
|
||||
return _SHIM_SO
|
||||
|
||||
|
||||
|
||||
_SHIM_SOURCE = r"""
|
||||
#define _GNU_SOURCE
|
||||
#include <dlfcn.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static int (*real_socket)(int, int, int);
|
||||
static int (*real_socketpair)(int, int, int, int[2]);
|
||||
static int (*real_listen)(int, int);
|
||||
static int (*real_accept)(int, struct sockaddr *, socklen_t *);
|
||||
static int (*real_close)(int);
|
||||
static int (*real_read)(int, void *, size_t);
|
||||
|
||||
/* Per-FD bookkeeping (FDs >= 1024 are passed through unshimmed). */
|
||||
static int is_shimmed[1024];
|
||||
static int peer_of[1024];
|
||||
static int wake_r[1024]; /* accept() blocks reading this */
|
||||
static int wake_w[1024]; /* close() writes to this */
|
||||
static int listener_fd = -1; /* FD that received listen() */
|
||||
|
||||
__attribute__((constructor))
|
||||
static void init(void) {
|
||||
real_socket = dlsym(RTLD_NEXT, "socket");
|
||||
real_socketpair = dlsym(RTLD_NEXT, "socketpair");
|
||||
real_listen = dlsym(RTLD_NEXT, "listen");
|
||||
real_accept = dlsym(RTLD_NEXT, "accept");
|
||||
real_close = dlsym(RTLD_NEXT, "close");
|
||||
real_read = dlsym(RTLD_NEXT, "read");
|
||||
for (int i = 0; i < 1024; i++) {
|
||||
peer_of[i] = -1;
|
||||
wake_r[i] = -1;
|
||||
wake_w[i] = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- socket ---------------------------------------------------------- */
|
||||
int socket(int domain, int type, int protocol) {
|
||||
if (domain == AF_UNIX) {
|
||||
int fd = real_socket(domain, type, protocol);
|
||||
if (fd >= 0) return fd;
|
||||
/* socket(AF_UNIX) blocked – fall back to socketpair(). */
|
||||
int sv[2];
|
||||
if (real_socketpair(domain, type, protocol, sv) == 0) {
|
||||
if (sv[0] >= 0 && sv[0] < 1024) {
|
||||
is_shimmed[sv[0]] = 1;
|
||||
peer_of[sv[0]] = sv[1];
|
||||
int wp[2];
|
||||
if (pipe(wp) == 0) {
|
||||
wake_r[sv[0]] = wp[0];
|
||||
wake_w[sv[0]] = wp[1];
|
||||
}
|
||||
}
|
||||
return sv[0];
|
||||
}
|
||||
errno = EPERM;
|
||||
return -1;
|
||||
}
|
||||
return real_socket(domain, type, protocol);
|
||||
}
|
||||
|
||||
/* ---- listen ---------------------------------------------------------- */
|
||||
int listen(int sockfd, int backlog) {
|
||||
if (sockfd >= 0 && sockfd < 1024 && is_shimmed[sockfd]) {
|
||||
listener_fd = sockfd;
|
||||
return 0;
|
||||
}
|
||||
return real_listen(sockfd, backlog);
|
||||
}
|
||||
|
||||
/* ---- accept ---------------------------------------------------------- */
|
||||
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) {
|
||||
if (sockfd >= 0 && sockfd < 1024 && is_shimmed[sockfd]) {
|
||||
/* Block until close() writes to the wake pipe. */
|
||||
if (wake_r[sockfd] >= 0) {
|
||||
char buf;
|
||||
real_read(wake_r[sockfd], &buf, 1);
|
||||
}
|
||||
errno = ECONNABORTED;
|
||||
return -1;
|
||||
}
|
||||
return real_accept(sockfd, addr, addrlen);
|
||||
}
|
||||
|
||||
/* ---- close ----------------------------------------------------------- */
|
||||
int close(int fd) {
|
||||
if (fd >= 0 && fd < 1024 && is_shimmed[fd]) {
|
||||
int was_listener = (fd == listener_fd);
|
||||
is_shimmed[fd] = 0;
|
||||
|
||||
if (wake_w[fd] >= 0) { /* unblock accept() */
|
||||
char c = 0;
|
||||
write(wake_w[fd], &c, 1);
|
||||
real_close(wake_w[fd]);
|
||||
wake_w[fd] = -1;
|
||||
}
|
||||
if (wake_r[fd] >= 0) { real_close(wake_r[fd]); wake_r[fd] = -1; }
|
||||
if (peer_of[fd] >= 0) { real_close(peer_of[fd]); peer_of[fd] = -1; }
|
||||
|
||||
if (was_listener)
|
||||
_exit(0); /* conversion done – exit */
|
||||
}
|
||||
return real_close(fd);
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
result = run_soffice(sys.argv[1:])
|
||||
sys.exit(result.returncode)
|
||||
@@ -1,132 +0,0 @@
|
||||
"""Unpack Office files (DOCX, PPTX, XLSX) for editing.
|
||||
|
||||
Extracts the ZIP archive, pretty-prints XML files, and optionally:
|
||||
- Merges adjacent runs with identical formatting (DOCX only)
|
||||
- Simplifies adjacent tracked changes from same author (DOCX only)
|
||||
|
||||
Usage:
|
||||
python unpack.py <office_file> <output_dir> [options]
|
||||
|
||||
Examples:
|
||||
python unpack.py document.docx unpacked/
|
||||
python unpack.py presentation.pptx unpacked/
|
||||
python unpack.py document.docx unpacked/ --merge-runs false
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
import defusedxml.minidom
|
||||
|
||||
from helpers.merge_runs import merge_runs as do_merge_runs
|
||||
from helpers.simplify_redlines import simplify_redlines as do_simplify_redlines
|
||||
|
||||
SMART_QUOTE_REPLACEMENTS = {
|
||||
"\u201c": "“",
|
||||
"\u201d": "”",
|
||||
"\u2018": "‘",
|
||||
"\u2019": "’",
|
||||
}
|
||||
|
||||
|
||||
def unpack(
|
||||
input_file: str,
|
||||
output_directory: str,
|
||||
merge_runs: bool = True,
|
||||
simplify_redlines: bool = True,
|
||||
) -> tuple[None, str]:
|
||||
input_path = Path(input_file)
|
||||
output_path = Path(output_directory)
|
||||
suffix = input_path.suffix.lower()
|
||||
|
||||
if not input_path.exists():
|
||||
return None, f"Error: {input_file} does not exist"
|
||||
|
||||
if suffix not in {".docx", ".pptx", ".xlsx"}:
|
||||
return None, f"Error: {input_file} must be a .docx, .pptx, or .xlsx file"
|
||||
|
||||
try:
|
||||
output_path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
with zipfile.ZipFile(input_path, "r") as zf:
|
||||
zf.extractall(output_path)
|
||||
|
||||
xml_files = list(output_path.rglob("*.xml")) + list(output_path.rglob("*.rels"))
|
||||
for xml_file in xml_files:
|
||||
_pretty_print_xml(xml_file)
|
||||
|
||||
message = f"Unpacked {input_file} ({len(xml_files)} XML files)"
|
||||
|
||||
if suffix == ".docx":
|
||||
if simplify_redlines:
|
||||
simplify_count, _ = do_simplify_redlines(str(output_path))
|
||||
message += f", simplified {simplify_count} tracked changes"
|
||||
|
||||
if merge_runs:
|
||||
merge_count, _ = do_merge_runs(str(output_path))
|
||||
message += f", merged {merge_count} runs"
|
||||
|
||||
for xml_file in xml_files:
|
||||
_escape_smart_quotes(xml_file)
|
||||
|
||||
return None, message
|
||||
|
||||
except zipfile.BadZipFile:
|
||||
return None, f"Error: {input_file} is not a valid Office file"
|
||||
except Exception as e:
|
||||
return None, f"Error unpacking: {e}"
|
||||
|
||||
|
||||
def _pretty_print_xml(xml_file: Path) -> None:
|
||||
try:
|
||||
content = xml_file.read_text(encoding="utf-8")
|
||||
dom = defusedxml.minidom.parseString(content)
|
||||
xml_file.write_bytes(dom.toprettyxml(indent=" ", encoding="utf-8"))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def _escape_smart_quotes(xml_file: Path) -> None:
|
||||
try:
|
||||
content = xml_file.read_text(encoding="utf-8")
|
||||
for char, entity in SMART_QUOTE_REPLACEMENTS.items():
|
||||
content = content.replace(char, entity)
|
||||
xml_file.write_text(content, encoding="utf-8")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Unpack an Office file (DOCX, PPTX, XLSX) for editing"
|
||||
)
|
||||
parser.add_argument("input_file", help="Office file to unpack")
|
||||
parser.add_argument("output_directory", help="Output directory")
|
||||
parser.add_argument(
|
||||
"--merge-runs",
|
||||
type=lambda x: x.lower() == "true",
|
||||
default=True,
|
||||
metavar="true|false",
|
||||
help="Merge adjacent runs with identical formatting (DOCX only, default: true)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--simplify-redlines",
|
||||
type=lambda x: x.lower() == "true",
|
||||
default=True,
|
||||
metavar="true|false",
|
||||
help="Merge adjacent tracked changes from same author (DOCX only, default: true)",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
_, message = unpack(
|
||||
args.input_file,
|
||||
args.output_directory,
|
||||
merge_runs=args.merge_runs,
|
||||
simplify_redlines=args.simplify_redlines,
|
||||
)
|
||||
print(message)
|
||||
|
||||
if "Error" in message:
|
||||
sys.exit(1)
|
||||
@@ -1,111 +0,0 @@
|
||||
"""
|
||||
Command line tool to validate Office document XML files against XSD schemas and tracked changes.
|
||||
|
||||
Usage:
|
||||
python validate.py <path> [--original <original_file>] [--auto-repair] [--author NAME]
|
||||
|
||||
The first argument can be either:
|
||||
- An unpacked directory containing the Office document XML files
|
||||
- A packed Office file (.docx/.pptx/.xlsx) which will be unpacked to a temp directory
|
||||
|
||||
Auto-repair fixes:
|
||||
- paraId/durableId values that exceed OOXML limits
|
||||
- Missing xml:space="preserve" on w:t elements with whitespace
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
import tempfile
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
from validators import DOCXSchemaValidator, PPTXSchemaValidator, RedliningValidator
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Validate Office document XML files")
|
||||
parser.add_argument(
|
||||
"path",
|
||||
help="Path to unpacked directory or packed Office file (.docx/.pptx/.xlsx)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--original",
|
||||
required=False,
|
||||
default=None,
|
||||
help="Path to original file (.docx/.pptx/.xlsx). If omitted, all XSD errors are reported and redlining validation is skipped.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-v",
|
||||
"--verbose",
|
||||
action="store_true",
|
||||
help="Enable verbose output",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--auto-repair",
|
||||
action="store_true",
|
||||
help="Automatically repair common issues (hex IDs, whitespace preservation)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--author",
|
||||
default="Claude",
|
||||
help="Author name for redlining validation (default: Claude)",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
path = Path(args.path)
|
||||
assert path.exists(), f"Error: {path} does not exist"
|
||||
|
||||
original_file = None
|
||||
if args.original:
|
||||
original_file = Path(args.original)
|
||||
assert original_file.is_file(), f"Error: {original_file} is not a file"
|
||||
assert original_file.suffix.lower() in [".docx", ".pptx", ".xlsx"], (
|
||||
f"Error: {original_file} must be a .docx, .pptx, or .xlsx file"
|
||||
)
|
||||
|
||||
file_extension = (original_file or path).suffix.lower()
|
||||
assert file_extension in [".docx", ".pptx", ".xlsx"], (
|
||||
f"Error: Cannot determine file type from {path}. Use --original or provide a .docx/.pptx/.xlsx file."
|
||||
)
|
||||
|
||||
if path.is_file() and path.suffix.lower() in [".docx", ".pptx", ".xlsx"]:
|
||||
temp_dir = tempfile.mkdtemp()
|
||||
with zipfile.ZipFile(path, "r") as zf:
|
||||
zf.extractall(temp_dir)
|
||||
unpacked_dir = Path(temp_dir)
|
||||
else:
|
||||
assert path.is_dir(), f"Error: {path} is not a directory or Office file"
|
||||
unpacked_dir = path
|
||||
|
||||
match file_extension:
|
||||
case ".docx":
|
||||
validators = [
|
||||
DOCXSchemaValidator(unpacked_dir, original_file, verbose=args.verbose),
|
||||
]
|
||||
if original_file:
|
||||
validators.append(
|
||||
RedliningValidator(unpacked_dir, original_file, verbose=args.verbose, author=args.author)
|
||||
)
|
||||
case ".pptx":
|
||||
validators = [
|
||||
PPTXSchemaValidator(unpacked_dir, original_file, verbose=args.verbose),
|
||||
]
|
||||
case _:
|
||||
print(f"Error: Validation not supported for file type {file_extension}")
|
||||
sys.exit(1)
|
||||
|
||||
if args.auto_repair:
|
||||
total_repairs = sum(v.repair() for v in validators)
|
||||
if total_repairs:
|
||||
print(f"Auto-repaired {total_repairs} issue(s)")
|
||||
|
||||
success = all(v.validate() for v in validators)
|
||||
|
||||
if success:
|
||||
print("All validations PASSED!")
|
||||
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,847 +0,0 @@
|
||||
"""
|
||||
Base validator with common validation logic for document files.
|
||||
"""
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
import defusedxml.minidom
|
||||
import lxml.etree
|
||||
|
||||
|
||||
class BaseSchemaValidator:
|
||||
|
||||
IGNORED_VALIDATION_ERRORS = [
|
||||
"hyphenationZone",
|
||||
"purl.org/dc/terms",
|
||||
]
|
||||
|
||||
UNIQUE_ID_REQUIREMENTS = {
|
||||
"comment": ("id", "file"),
|
||||
"commentrangestart": ("id", "file"),
|
||||
"commentrangeend": ("id", "file"),
|
||||
"bookmarkstart": ("id", "file"),
|
||||
"bookmarkend": ("id", "file"),
|
||||
"sldid": ("id", "file"),
|
||||
"sldmasterid": ("id", "global"),
|
||||
"sldlayoutid": ("id", "global"),
|
||||
"cm": ("authorid", "file"),
|
||||
"sheet": ("sheetid", "file"),
|
||||
"definedname": ("id", "file"),
|
||||
"cxnsp": ("id", "file"),
|
||||
"sp": ("id", "file"),
|
||||
"pic": ("id", "file"),
|
||||
"grpsp": ("id", "file"),
|
||||
}
|
||||
|
||||
EXCLUDED_ID_CONTAINERS = {
|
||||
"sectionlst",
|
||||
}
|
||||
|
||||
ELEMENT_RELATIONSHIP_TYPES = {}
|
||||
|
||||
SCHEMA_MAPPINGS = {
|
||||
"word": "ISO-IEC29500-4_2016/wml.xsd",
|
||||
"ppt": "ISO-IEC29500-4_2016/pml.xsd",
|
||||
"xl": "ISO-IEC29500-4_2016/sml.xsd",
|
||||
"[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",
|
||||
"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": "ISO-IEC29500-4_2016/dml-chart.xsd",
|
||||
"theme": "ISO-IEC29500-4_2016/dml-main.xsd",
|
||||
"drawing": "ISO-IEC29500-4_2016/dml-main.xsd",
|
||||
}
|
||||
|
||||
MC_NAMESPACE = "http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
XML_NAMESPACE = "http://www.w3.org/XML/1998/namespace"
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
MAIN_CONTENT_FOLDERS = {"word", "ppt", "xl"}
|
||||
|
||||
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=None, verbose=False):
|
||||
self.unpacked_dir = Path(unpacked_dir).resolve()
|
||||
self.original_file = Path(original_file) if original_file else None
|
||||
self.verbose = verbose
|
||||
|
||||
self.schemas_dir = Path(__file__).parent.parent / "schemas"
|
||||
|
||||
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):
|
||||
raise NotImplementedError("Subclasses must implement the validate method")
|
||||
|
||||
def repair(self) -> int:
|
||||
return self.repair_whitespace_preservation()
|
||||
|
||||
def repair_whitespace_preservation(self) -> int:
|
||||
repairs = 0
|
||||
|
||||
for xml_file in self.xml_files:
|
||||
try:
|
||||
content = xml_file.read_text(encoding="utf-8")
|
||||
dom = defusedxml.minidom.parseString(content)
|
||||
modified = False
|
||||
|
||||
for elem in dom.getElementsByTagName("*"):
|
||||
if elem.tagName.endswith(":t") and elem.firstChild:
|
||||
text = elem.firstChild.nodeValue
|
||||
if text and (text.startswith((' ', '\t')) or text.endswith((' ', '\t'))):
|
||||
if elem.getAttribute("xml:space") != "preserve":
|
||||
elem.setAttribute("xml:space", "preserve")
|
||||
text_preview = repr(text[:30]) + "..." if len(text) > 30 else repr(text)
|
||||
print(f" Repaired: {xml_file.name}: Added xml:space='preserve' to {elem.tagName}: {text_preview}")
|
||||
repairs += 1
|
||||
modified = True
|
||||
|
||||
if modified:
|
||||
xml_file.write_bytes(dom.toxml(encoding="UTF-8"))
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return repairs
|
||||
|
||||
def validate_xml(self):
|
||||
errors = []
|
||||
|
||||
for xml_file in self.xml_files:
|
||||
try:
|
||||
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):
|
||||
errors = []
|
||||
|
||||
for xml_file in self.xml_files:
|
||||
try:
|
||||
root = lxml.etree.parse(str(xml_file)).getroot()
|
||||
declared = set(root.nsmap.keys()) - {None}
|
||||
|
||||
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):
|
||||
errors = []
|
||||
global_ids = {}
|
||||
|
||||
for xml_file in self.xml_files:
|
||||
try:
|
||||
root = lxml.etree.parse(str(xml_file)).getroot()
|
||||
file_ids = {}
|
||||
|
||||
mc_elements = root.xpath(
|
||||
".//mc:AlternateContent", namespaces={"mc": self.MC_NAMESPACE}
|
||||
)
|
||||
for elem in mc_elements:
|
||||
elem.getparent().remove(elem)
|
||||
|
||||
for elem in root.iter():
|
||||
tag = (
|
||||
elem.tag.split("}")[-1].lower()
|
||||
if "}" in elem.tag
|
||||
else elem.tag.lower()
|
||||
)
|
||||
|
||||
if tag in self.UNIQUE_ID_REQUIREMENTS:
|
||||
in_excluded_container = any(
|
||||
ancestor.tag.split("}")[-1].lower() in self.EXCLUDED_ID_CONTAINERS
|
||||
for ancestor in elem.iterancestors()
|
||||
)
|
||||
if in_excluded_container:
|
||||
continue
|
||||
|
||||
attr_name, scope = self.UNIQUE_ID_REQUIREMENTS[tag]
|
||||
|
||||
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":
|
||||
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":
|
||||
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):
|
||||
errors = []
|
||||
|
||||
rels_files = list(self.unpacked_dir.rglob("*.rels"))
|
||||
|
||||
if not rels_files:
|
||||
if self.verbose:
|
||||
print("PASSED - No .rels files found")
|
||||
return True
|
||||
|
||||
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")
|
||||
):
|
||||
all_files.append(file_path.resolve())
|
||||
|
||||
all_referenced_files = set()
|
||||
|
||||
if self.verbose:
|
||||
print(
|
||||
f"Found {len(rels_files)} .rels files and {len(all_files)} target files"
|
||||
)
|
||||
|
||||
for rels_file in rels_files:
|
||||
try:
|
||||
rels_root = lxml.etree.parse(str(rels_file)).getroot()
|
||||
|
||||
rels_dir = rels_file.parent
|
||||
|
||||
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:")
|
||||
):
|
||||
if target.startswith("/"):
|
||||
target_path = self.unpacked_dir / target.lstrip("/")
|
||||
elif rels_file.name == ".rels":
|
||||
target_path = self.unpacked_dir / target
|
||||
else:
|
||||
base_dir = rels_dir.parent
|
||||
target_path = base_dir / target
|
||||
|
||||
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))
|
||||
|
||||
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}")
|
||||
|
||||
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):
|
||||
import lxml.etree
|
||||
|
||||
errors = []
|
||||
|
||||
for xml_file in self.xml_files:
|
||||
if xml_file.suffix == ".rels":
|
||||
continue
|
||||
|
||||
rels_dir = xml_file.parent / "_rels"
|
||||
rels_file = rels_dir / f"{xml_file.name}.rels"
|
||||
|
||||
if not rels_file.exists():
|
||||
continue
|
||||
|
||||
try:
|
||||
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:
|
||||
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)"
|
||||
)
|
||||
type_name = (
|
||||
rel_type.split("/")[-1] if "/" in rel_type else rel_type
|
||||
)
|
||||
rid_to_type[rid] = type_name
|
||||
|
||||
xml_root = lxml.etree.parse(str(xml_file)).getroot()
|
||||
|
||||
r_ns = self.OFFICE_RELATIONSHIPS_NAMESPACE
|
||||
rid_attrs_to_check = ["id", "embed", "link"]
|
||||
for elem in xml_root.iter():
|
||||
for attr_name in rid_attrs_to_check:
|
||||
rid_attr = elem.get(f"{{{r_ns}}}{attr_name}")
|
||||
if not rid_attr:
|
||||
continue
|
||||
xml_rel_path = xml_file.relative_to(self.unpacked_dir)
|
||||
elem_name = (
|
||||
elem.tag.split("}")[-1] if "}" in elem.tag else elem.tag
|
||||
)
|
||||
|
||||
if rid_attr not in rid_to_type:
|
||||
errors.append(
|
||||
f" {xml_rel_path}: Line {elem.sourceline}: "
|
||||
f"<{elem_name}> r:{attr_name} references non-existent relationship '{rid_attr}' "
|
||||
f"(valid IDs: {', '.join(sorted(rid_to_type.keys())[:5])}{'...' if len(rid_to_type) > 5 else ''})"
|
||||
)
|
||||
elif attr_name == "id" and self.ELEMENT_RELATIONSHIP_TYPES:
|
||||
expected_type = self._get_expected_relationship_type(
|
||||
elem_name
|
||||
)
|
||||
if expected_type:
|
||||
actual_type = rid_to_type[rid_attr]
|
||||
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):
|
||||
elem_lower = element_name.lower()
|
||||
|
||||
if elem_lower in self.ELEMENT_RELATIONSHIP_TYPES:
|
||||
return self.ELEMENT_RELATIONSHIP_TYPES[elem_lower]
|
||||
|
||||
if elem_lower.endswith("id") and len(elem_lower) > 2:
|
||||
prefix = elem_lower[:-2]
|
||||
if prefix.endswith("master"):
|
||||
return prefix.lower()
|
||||
elif prefix.endswith("layout"):
|
||||
return prefix.lower()
|
||||
else:
|
||||
if prefix == "sld":
|
||||
return "slide"
|
||||
return prefix.lower()
|
||||
|
||||
if elem_lower.endswith("reference") and len(elem_lower) > 9:
|
||||
prefix = elem_lower[:-9]
|
||||
return prefix.lower()
|
||||
|
||||
return None
|
||||
|
||||
def validate_content_types(self):
|
||||
errors = []
|
||||
|
||||
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:
|
||||
root = lxml.etree.parse(str(content_types_file)).getroot()
|
||||
declared_parts = set()
|
||||
declared_extensions = set()
|
||||
|
||||
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("/"))
|
||||
|
||||
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())
|
||||
|
||||
declarable_roots = {
|
||||
"sld",
|
||||
"sldLayout",
|
||||
"sldMaster",
|
||||
"presentation",
|
||||
"document",
|
||||
"workbook",
|
||||
"worksheet",
|
||||
"theme",
|
||||
}
|
||||
|
||||
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",
|
||||
}
|
||||
|
||||
all_files = list(self.unpacked_dir.rglob("*"))
|
||||
all_files = [f for f in all_files if f.is_file()]
|
||||
|
||||
for xml_file in self.xml_files:
|
||||
path_str = str(xml_file.relative_to(self.unpacked_dir)).replace(
|
||||
"\\", "/"
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
for file_path in all_files:
|
||||
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:
|
||||
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):
|
||||
xml_file = Path(xml_file).resolve()
|
||||
unpacked_dir = self.unpacked_dir.resolve()
|
||||
|
||||
is_valid, current_errors = self._validate_single_file_xsd(
|
||||
xml_file, unpacked_dir
|
||||
)
|
||||
|
||||
if is_valid is None:
|
||||
return None, set()
|
||||
elif is_valid:
|
||||
return True, set()
|
||||
|
||||
original_errors = self._get_original_file_errors(xml_file)
|
||||
|
||||
assert current_errors is not None
|
||||
new_errors = current_errors - original_errors
|
||||
|
||||
new_errors = {
|
||||
e for e in new_errors
|
||||
if not any(pattern in e for pattern in self.IGNORED_VALIDATION_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:
|
||||
if verbose:
|
||||
print(
|
||||
f"PASSED - No new errors (original had {len(current_errors)} errors)"
|
||||
)
|
||||
return True, set()
|
||||
|
||||
def validate_against_xsd(self):
|
||||
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:
|
||||
original_error_count += 1
|
||||
valid_count += 1
|
||||
continue
|
||||
|
||||
new_errors.append(f" {relative_path}: {len(new_file_errors)} new error(s)")
|
||||
for error in list(new_file_errors)[:3]:
|
||||
new_errors.append(
|
||||
f" - {error[:250]}..." if len(error) > 250 else f" - {error}"
|
||||
)
|
||||
|
||||
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):
|
||||
if xml_file.name in self.SCHEMA_MAPPINGS:
|
||||
return self.schemas_dir / self.SCHEMA_MAPPINGS[xml_file.name]
|
||||
|
||||
if xml_file.suffix == ".rels":
|
||||
return self.schemas_dir / self.SCHEMA_MAPPINGS[".rels"]
|
||||
|
||||
if "charts/" in str(xml_file) and xml_file.name.startswith("chart"):
|
||||
return self.schemas_dir / self.SCHEMA_MAPPINGS["chart"]
|
||||
|
||||
if "theme/" in str(xml_file) and xml_file.name.startswith("theme"):
|
||||
return self.schemas_dir / self.SCHEMA_MAPPINGS["theme"]
|
||||
|
||||
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):
|
||||
xml_string = lxml.etree.tostring(xml_doc, encoding="unicode")
|
||||
xml_copy = lxml.etree.fromstring(xml_string)
|
||||
|
||||
for elem in xml_copy.iter():
|
||||
attrs_to_remove = []
|
||||
|
||||
for attr in elem.attrib:
|
||||
if "{" in attr:
|
||||
ns = attr.split("}")[0][1:]
|
||||
if ns not in self.OOXML_NAMESPACES:
|
||||
attrs_to_remove.append(attr)
|
||||
|
||||
for attr in attrs_to_remove:
|
||||
del elem.attrib[attr]
|
||||
|
||||
self._remove_ignorable_elements(xml_copy)
|
||||
|
||||
return lxml.etree.ElementTree(xml_copy)
|
||||
|
||||
def _remove_ignorable_elements(self, root):
|
||||
elements_to_remove = []
|
||||
|
||||
for elem in list(root):
|
||||
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
|
||||
|
||||
self._remove_ignorable_elements(elem)
|
||||
|
||||
for elem in elements_to_remove:
|
||||
root.remove(elem)
|
||||
|
||||
def _preprocess_for_mc_ignorable(self, xml_doc):
|
||||
root = xml_doc.getroot()
|
||||
|
||||
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):
|
||||
schema_path = self._get_schema_path(xml_file)
|
||||
if not schema_path:
|
||||
return None, None
|
||||
|
||||
try:
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
if schema.validate(xml_doc):
|
||||
return True, set()
|
||||
else:
|
||||
errors = set()
|
||||
for error in schema.error_log:
|
||||
errors.add(error.message)
|
||||
return False, errors
|
||||
|
||||
except Exception as e:
|
||||
return False, {str(e)}
|
||||
|
||||
def _get_original_file_errors(self, xml_file):
|
||||
if self.original_file is None:
|
||||
return set()
|
||||
|
||||
import tempfile
|
||||
import zipfile
|
||||
|
||||
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)
|
||||
|
||||
with zipfile.ZipFile(self.original_file, "r") as zip_ref:
|
||||
zip_ref.extractall(temp_path)
|
||||
|
||||
original_xml_file = temp_path / relative_path
|
||||
|
||||
if not original_xml_file.exists():
|
||||
return set()
|
||||
|
||||
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):
|
||||
warnings = []
|
||||
template_pattern = re.compile(r"\{\{[^}]*\}\}")
|
||||
|
||||
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
|
||||
|
||||
for elem in xml_copy.iter():
|
||||
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.")
|
||||
@@ -1,446 +0,0 @@
|
||||
"""
|
||||
Validator for Word document XML files against XSD schemas.
|
||||
"""
|
||||
|
||||
import random
|
||||
import re
|
||||
import tempfile
|
||||
import zipfile
|
||||
|
||||
import defusedxml.minidom
|
||||
import lxml.etree
|
||||
|
||||
from .base import BaseSchemaValidator
|
||||
|
||||
|
||||
class DOCXSchemaValidator(BaseSchemaValidator):
|
||||
|
||||
WORD_2006_NAMESPACE = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
|
||||
W14_NAMESPACE = "http://schemas.microsoft.com/office/word/2010/wordml"
|
||||
W16CID_NAMESPACE = "http://schemas.microsoft.com/office/word/2016/wordml/cid"
|
||||
|
||||
ELEMENT_RELATIONSHIP_TYPES = {}
|
||||
|
||||
def validate(self):
|
||||
if not self.validate_xml():
|
||||
return False
|
||||
|
||||
all_valid = True
|
||||
if not self.validate_namespaces():
|
||||
all_valid = False
|
||||
|
||||
if not self.validate_unique_ids():
|
||||
all_valid = False
|
||||
|
||||
if not self.validate_file_references():
|
||||
all_valid = False
|
||||
|
||||
if not self.validate_content_types():
|
||||
all_valid = False
|
||||
|
||||
if not self.validate_against_xsd():
|
||||
all_valid = False
|
||||
|
||||
if not self.validate_whitespace_preservation():
|
||||
all_valid = False
|
||||
|
||||
if not self.validate_deletions():
|
||||
all_valid = False
|
||||
|
||||
if not self.validate_insertions():
|
||||
all_valid = False
|
||||
|
||||
if not self.validate_all_relationship_ids():
|
||||
all_valid = False
|
||||
|
||||
if not self.validate_id_constraints():
|
||||
all_valid = False
|
||||
|
||||
if not self.validate_comment_markers():
|
||||
all_valid = False
|
||||
|
||||
self.compare_paragraph_counts()
|
||||
|
||||
return all_valid
|
||||
|
||||
def validate_whitespace_preservation(self):
|
||||
errors = []
|
||||
|
||||
for xml_file in self.xml_files:
|
||||
if xml_file.name != "document.xml":
|
||||
continue
|
||||
|
||||
try:
|
||||
root = lxml.etree.parse(str(xml_file)).getroot()
|
||||
|
||||
for elem in root.iter(f"{{{self.WORD_2006_NAMESPACE}}}t"):
|
||||
if elem.text:
|
||||
text = elem.text
|
||||
if re.search(r"^[ \t\n\r]", text) or re.search(
|
||||
r"[ \t\n\r]$", text
|
||||
):
|
||||
xml_space_attr = f"{{{self.XML_NAMESPACE}}}space"
|
||||
if (
|
||||
xml_space_attr not in elem.attrib
|
||||
or elem.attrib[xml_space_attr] != "preserve"
|
||||
):
|
||||
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):
|
||||
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}
|
||||
|
||||
for t_elem in root.xpath(".//w:del//w:t", namespaces=namespaces):
|
||||
if t_elem.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}"
|
||||
)
|
||||
|
||||
for instr_elem in root.xpath(
|
||||
".//w:del//w:instrText", namespaces=namespaces
|
||||
):
|
||||
text_preview = (
|
||||
repr(instr_elem.text or "")[:50] + "..."
|
||||
if len(repr(instr_elem.text or "")) > 50
|
||||
else repr(instr_elem.text or "")
|
||||
)
|
||||
errors.append(
|
||||
f" {xml_file.relative_to(self.unpacked_dir)}: "
|
||||
f"Line {instr_elem.sourceline}: <w:instrText> found within <w:del> (use <w:delInstrText>): {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 = 0
|
||||
|
||||
for xml_file in self.xml_files:
|
||||
if xml_file.name != "document.xml":
|
||||
continue
|
||||
|
||||
try:
|
||||
root = lxml.etree.parse(str(xml_file)).getroot()
|
||||
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):
|
||||
original = self.original_file
|
||||
if original is None:
|
||||
return 0
|
||||
|
||||
count = 0
|
||||
|
||||
try:
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
with zipfile.ZipFile(original, "r") as zip_ref:
|
||||
zip_ref.extractall(temp_dir)
|
||||
|
||||
doc_xml_path = temp_dir + "/word/document.xml"
|
||||
root = lxml.etree.parse(doc_xml_path).getroot()
|
||||
|
||||
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):
|
||||
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}
|
||||
|
||||
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):
|
||||
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})")
|
||||
|
||||
def _parse_id_value(self, val: str, base: int = 16) -> int:
|
||||
return int(val, base)
|
||||
|
||||
def validate_id_constraints(self):
|
||||
errors = []
|
||||
para_id_attr = f"{{{self.W14_NAMESPACE}}}paraId"
|
||||
durable_id_attr = f"{{{self.W16CID_NAMESPACE}}}durableId"
|
||||
|
||||
for xml_file in self.xml_files:
|
||||
try:
|
||||
for elem in lxml.etree.parse(str(xml_file)).iter():
|
||||
if val := elem.get(para_id_attr):
|
||||
if self._parse_id_value(val, base=16) >= 0x80000000:
|
||||
errors.append(
|
||||
f" {xml_file.name}:{elem.sourceline}: paraId={val} >= 0x80000000"
|
||||
)
|
||||
|
||||
if val := elem.get(durable_id_attr):
|
||||
if xml_file.name == "numbering.xml":
|
||||
try:
|
||||
if self._parse_id_value(val, base=10) >= 0x7FFFFFFF:
|
||||
errors.append(
|
||||
f" {xml_file.name}:{elem.sourceline}: "
|
||||
f"durableId={val} >= 0x7FFFFFFF"
|
||||
)
|
||||
except ValueError:
|
||||
errors.append(
|
||||
f" {xml_file.name}:{elem.sourceline}: "
|
||||
f"durableId={val} must be decimal in numbering.xml"
|
||||
)
|
||||
else:
|
||||
if self._parse_id_value(val, base=16) >= 0x7FFFFFFF:
|
||||
errors.append(
|
||||
f" {xml_file.name}:{elem.sourceline}: "
|
||||
f"durableId={val} >= 0x7FFFFFFF"
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if errors:
|
||||
print(f"FAILED - {len(errors)} ID constraint violations:")
|
||||
for e in errors:
|
||||
print(e)
|
||||
elif self.verbose:
|
||||
print("PASSED - All paraId/durableId values within constraints")
|
||||
return not errors
|
||||
|
||||
def validate_comment_markers(self):
|
||||
errors = []
|
||||
|
||||
document_xml = None
|
||||
comments_xml = None
|
||||
for xml_file in self.xml_files:
|
||||
if xml_file.name == "document.xml" and "word" in str(xml_file):
|
||||
document_xml = xml_file
|
||||
elif xml_file.name == "comments.xml":
|
||||
comments_xml = xml_file
|
||||
|
||||
if not document_xml:
|
||||
if self.verbose:
|
||||
print("PASSED - No document.xml found (skipping comment validation)")
|
||||
return True
|
||||
|
||||
try:
|
||||
doc_root = lxml.etree.parse(str(document_xml)).getroot()
|
||||
namespaces = {"w": self.WORD_2006_NAMESPACE}
|
||||
|
||||
range_starts = {
|
||||
elem.get(f"{{{self.WORD_2006_NAMESPACE}}}id")
|
||||
for elem in doc_root.xpath(
|
||||
".//w:commentRangeStart", namespaces=namespaces
|
||||
)
|
||||
}
|
||||
range_ends = {
|
||||
elem.get(f"{{{self.WORD_2006_NAMESPACE}}}id")
|
||||
for elem in doc_root.xpath(
|
||||
".//w:commentRangeEnd", namespaces=namespaces
|
||||
)
|
||||
}
|
||||
references = {
|
||||
elem.get(f"{{{self.WORD_2006_NAMESPACE}}}id")
|
||||
for elem in doc_root.xpath(
|
||||
".//w:commentReference", namespaces=namespaces
|
||||
)
|
||||
}
|
||||
|
||||
orphaned_ends = range_ends - range_starts
|
||||
for comment_id in sorted(
|
||||
orphaned_ends, key=lambda x: int(x) if x and x.isdigit() else 0
|
||||
):
|
||||
errors.append(
|
||||
f' document.xml: commentRangeEnd id="{comment_id}" has no matching commentRangeStart'
|
||||
)
|
||||
|
||||
orphaned_starts = range_starts - range_ends
|
||||
for comment_id in sorted(
|
||||
orphaned_starts, key=lambda x: int(x) if x and x.isdigit() else 0
|
||||
):
|
||||
errors.append(
|
||||
f' document.xml: commentRangeStart id="{comment_id}" has no matching commentRangeEnd'
|
||||
)
|
||||
|
||||
comment_ids = set()
|
||||
if comments_xml and comments_xml.exists():
|
||||
comments_root = lxml.etree.parse(str(comments_xml)).getroot()
|
||||
comment_ids = {
|
||||
elem.get(f"{{{self.WORD_2006_NAMESPACE}}}id")
|
||||
for elem in comments_root.xpath(
|
||||
".//w:comment", namespaces=namespaces
|
||||
)
|
||||
}
|
||||
|
||||
marker_ids = range_starts | range_ends | references
|
||||
invalid_refs = marker_ids - comment_ids
|
||||
for comment_id in sorted(
|
||||
invalid_refs, key=lambda x: int(x) if x and x.isdigit() else 0
|
||||
):
|
||||
if comment_id:
|
||||
errors.append(
|
||||
f' document.xml: marker id="{comment_id}" references non-existent comment'
|
||||
)
|
||||
|
||||
except (lxml.etree.XMLSyntaxError, Exception) as e:
|
||||
errors.append(f" Error parsing XML: {e}")
|
||||
|
||||
if errors:
|
||||
print(f"FAILED - {len(errors)} comment marker violations:")
|
||||
for error in errors:
|
||||
print(error)
|
||||
return False
|
||||
else:
|
||||
if self.verbose:
|
||||
print("PASSED - All comment markers properly paired")
|
||||
return True
|
||||
|
||||
def repair(self) -> int:
|
||||
repairs = super().repair()
|
||||
repairs += self.repair_durableId()
|
||||
return repairs
|
||||
|
||||
def repair_durableId(self) -> int:
|
||||
repairs = 0
|
||||
|
||||
for xml_file in self.xml_files:
|
||||
try:
|
||||
content = xml_file.read_text(encoding="utf-8")
|
||||
dom = defusedxml.minidom.parseString(content)
|
||||
modified = False
|
||||
|
||||
for elem in dom.getElementsByTagName("*"):
|
||||
if not elem.hasAttribute("w16cid:durableId"):
|
||||
continue
|
||||
|
||||
durable_id = elem.getAttribute("w16cid:durableId")
|
||||
needs_repair = False
|
||||
|
||||
if xml_file.name == "numbering.xml":
|
||||
try:
|
||||
needs_repair = (
|
||||
self._parse_id_value(durable_id, base=10) >= 0x7FFFFFFF
|
||||
)
|
||||
except ValueError:
|
||||
needs_repair = True
|
||||
else:
|
||||
try:
|
||||
needs_repair = (
|
||||
self._parse_id_value(durable_id, base=16) >= 0x7FFFFFFF
|
||||
)
|
||||
except ValueError:
|
||||
needs_repair = True
|
||||
|
||||
if needs_repair:
|
||||
value = random.randint(1, 0x7FFFFFFE)
|
||||
if xml_file.name == "numbering.xml":
|
||||
new_id = str(value)
|
||||
else:
|
||||
new_id = f"{value:08X}"
|
||||
|
||||
elem.setAttribute("w16cid:durableId", new_id)
|
||||
print(
|
||||
f" Repaired: {xml_file.name}: durableId {durable_id} → {new_id}"
|
||||
)
|
||||
repairs += 1
|
||||
modified = True
|
||||
|
||||
if modified:
|
||||
xml_file.write_bytes(dom.toxml(encoding="UTF-8"))
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return repairs
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise RuntimeError("This module should not be run directly.")
|
||||
@@ -1,275 +0,0 @@
|
||||
"""
|
||||
Validator for PowerPoint presentation XML files against XSD schemas.
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from .base import BaseSchemaValidator
|
||||
|
||||
|
||||
class PPTXSchemaValidator(BaseSchemaValidator):
|
||||
|
||||
PRESENTATIONML_NAMESPACE = (
|
||||
"http://schemas.openxmlformats.org/presentationml/2006/main"
|
||||
)
|
||||
|
||||
ELEMENT_RELATIONSHIP_TYPES = {
|
||||
"sldid": "slide",
|
||||
"sldmasterid": "slidemaster",
|
||||
"notesmasterid": "notesmaster",
|
||||
"sldlayoutid": "slidelayout",
|
||||
"themeid": "theme",
|
||||
"tablestyleid": "tablestyles",
|
||||
}
|
||||
|
||||
def validate(self):
|
||||
if not self.validate_xml():
|
||||
return False
|
||||
|
||||
all_valid = True
|
||||
if not self.validate_namespaces():
|
||||
all_valid = False
|
||||
|
||||
if not self.validate_unique_ids():
|
||||
all_valid = False
|
||||
|
||||
if not self.validate_uuid_ids():
|
||||
all_valid = False
|
||||
|
||||
if not self.validate_file_references():
|
||||
all_valid = False
|
||||
|
||||
if not self.validate_slide_layout_ids():
|
||||
all_valid = False
|
||||
|
||||
if not self.validate_content_types():
|
||||
all_valid = False
|
||||
|
||||
if not self.validate_against_xsd():
|
||||
all_valid = False
|
||||
|
||||
if not self.validate_notes_slide_references():
|
||||
all_valid = False
|
||||
|
||||
if not self.validate_all_relationship_ids():
|
||||
all_valid = False
|
||||
|
||||
if not self.validate_no_duplicate_slide_layouts():
|
||||
all_valid = False
|
||||
|
||||
return all_valid
|
||||
|
||||
def validate_uuid_ids(self):
|
||||
import lxml.etree
|
||||
|
||||
errors = []
|
||||
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()
|
||||
|
||||
for elem in root.iter():
|
||||
for attr, value in elem.attrib.items():
|
||||
attr_name = attr.split("}")[-1].lower()
|
||||
if attr_name == "id" or attr_name.endswith("id"):
|
||||
if self._looks_like_uuid(value):
|
||||
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):
|
||||
clean_value = value.strip("{}()").replace("-", "")
|
||||
return len(clean_value) == 32 and all(c.isalnum() for c in clean_value)
|
||||
|
||||
def validate_slide_layout_ids(self):
|
||||
import lxml.etree
|
||||
|
||||
errors = []
|
||||
|
||||
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:
|
||||
root = lxml.etree.parse(str(slide_master)).getroot()
|
||||
|
||||
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
|
||||
|
||||
rels_root = lxml.etree.parse(str(rels_file)).getroot()
|
||||
|
||||
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"))
|
||||
|
||||
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):
|
||||
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()
|
||||
|
||||
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):
|
||||
import lxml.etree
|
||||
|
||||
errors = []
|
||||
notes_slide_references = {}
|
||||
|
||||
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:
|
||||
root = lxml.etree.parse(str(rels_file)).getroot()
|
||||
|
||||
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:
|
||||
normalized_target = target.replace("../", "")
|
||||
|
||||
slide_name = rels_file.stem.replace(
|
||||
".xml", ""
|
||||
)
|
||||
|
||||
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}"
|
||||
)
|
||||
|
||||
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.")
|
||||
@@ -1,247 +0,0 @@
|
||||
"""
|
||||
Validator for tracked changes in Word documents.
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import tempfile
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class RedliningValidator:
|
||||
|
||||
def __init__(self, unpacked_dir, original_docx, verbose=False, author="Claude"):
|
||||
self.unpacked_dir = Path(unpacked_dir)
|
||||
self.original_docx = Path(original_docx)
|
||||
self.verbose = verbose
|
||||
self.author = author
|
||||
self.namespaces = {
|
||||
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
|
||||
}
|
||||
|
||||
def repair(self) -> int:
|
||||
return 0
|
||||
|
||||
def validate(self):
|
||||
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
|
||||
|
||||
try:
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
tree = ET.parse(modified_file)
|
||||
root = tree.getroot()
|
||||
|
||||
del_elements = root.findall(".//w:del", self.namespaces)
|
||||
ins_elements = root.findall(".//w:ins", self.namespaces)
|
||||
|
||||
author_del_elements = [
|
||||
elem
|
||||
for elem in del_elements
|
||||
if elem.get(f"{{{self.namespaces['w']}}}author") == self.author
|
||||
]
|
||||
author_ins_elements = [
|
||||
elem
|
||||
for elem in ins_elements
|
||||
if elem.get(f"{{{self.namespaces['w']}}}author") == self.author
|
||||
]
|
||||
|
||||
if not author_del_elements and not author_ins_elements:
|
||||
if self.verbose:
|
||||
print(f"PASSED - No tracked changes by {self.author} found.")
|
||||
return True
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
temp_path = Path(temp_dir)
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
self._remove_author_tracked_changes(original_root)
|
||||
self._remove_author_tracked_changes(modified_root)
|
||||
|
||||
modified_text = self._extract_text_content(modified_root)
|
||||
original_text = self._extract_text_content(original_root)
|
||||
|
||||
if modified_text != original_text:
|
||||
error_message = self._generate_detailed_diff(
|
||||
original_text, modified_text
|
||||
)
|
||||
print(error_message)
|
||||
return False
|
||||
|
||||
if self.verbose:
|
||||
print(f"PASSED - All changes by {self.author} are properly tracked")
|
||||
return True
|
||||
|
||||
def _generate_detailed_diff(self, original_text, modified_text):
|
||||
error_parts = [
|
||||
f"FAILED - Document text doesn't match after removing {self.author}'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>",
|
||||
"",
|
||||
]
|
||||
|
||||
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):
|
||||
try:
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
temp_path = Path(temp_dir)
|
||||
|
||||
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")
|
||||
|
||||
result = subprocess.run(
|
||||
[
|
||||
"git",
|
||||
"diff",
|
||||
"--word-diff=plain",
|
||||
"--word-diff-regex=.",
|
||||
"-U0",
|
||||
"--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)
|
||||
|
||||
if content_lines:
|
||||
return "\n".join(content_lines)
|
||||
|
||||
result = subprocess.run(
|
||||
[
|
||||
"git",
|
||||
"diff",
|
||||
"--word-diff=plain",
|
||||
"-U0",
|
||||
"--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):
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
def _remove_author_tracked_changes(self, root):
|
||||
ins_tag = f"{{{self.namespaces['w']}}}ins"
|
||||
del_tag = f"{{{self.namespaces['w']}}}del"
|
||||
author_attr = f"{{{self.namespaces['w']}}}author"
|
||||
|
||||
for parent in root.iter():
|
||||
to_remove = []
|
||||
for child in parent:
|
||||
if child.tag == ins_tag and child.get(author_attr) == self.author:
|
||||
to_remove.append(child)
|
||||
for elem in to_remove:
|
||||
parent.remove(elem)
|
||||
|
||||
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) == self.author:
|
||||
to_process.append((child, list(parent).index(child)))
|
||||
|
||||
for del_elem, del_index in reversed(to_process):
|
||||
for elem in del_elem.iter():
|
||||
if elem.tag == deltext_tag:
|
||||
elem.tag = t_tag
|
||||
|
||||
for child in reversed(list(del_elem)):
|
||||
parent.insert(del_index, child)
|
||||
parent.remove(del_elem)
|
||||
|
||||
def _extract_text_content(self, root):
|
||||
p_tag = f"{{{self.namespaces['w']}}}p"
|
||||
t_tag = f"{{{self.namespaces['w']}}}t"
|
||||
|
||||
paragraphs = []
|
||||
for p_elem in root.findall(f".//{p_tag}"):
|
||||
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)
|
||||
if paragraph_text:
|
||||
paragraphs.append(paragraph_text)
|
||||
|
||||
return "\n".join(paragraphs)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise RuntimeError("This module should not be run directly.")
|
||||
@@ -1,184 +0,0 @@
|
||||
"""
|
||||
Excel Formula Recalculation Script
|
||||
Recalculates all formulas in an Excel file using LibreOffice
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import platform
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from office.soffice import get_soffice_env
|
||||
|
||||
from openpyxl import load_workbook
|
||||
|
||||
MACRO_DIR_MACOS = "~/Library/Application Support/LibreOffice/4/user/basic/Standard"
|
||||
MACRO_DIR_LINUX = "~/.config/libreoffice/4/user/basic/Standard"
|
||||
MACRO_FILENAME = "Module1.xba"
|
||||
|
||||
RECALCULATE_MACRO = """<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE script:module PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "module.dtd">
|
||||
<script:module xmlns:script="http://openoffice.org/2000/script" script:name="Module1" script:language="StarBasic">
|
||||
Sub RecalculateAndSave()
|
||||
ThisComponent.calculateAll()
|
||||
ThisComponent.store()
|
||||
ThisComponent.close(True)
|
||||
End Sub
|
||||
</script:module>"""
|
||||
|
||||
|
||||
def has_gtimeout():
|
||||
try:
|
||||
subprocess.run(
|
||||
["gtimeout", "--version"], capture_output=True, timeout=1, check=False
|
||||
)
|
||||
return True
|
||||
except (FileNotFoundError, subprocess.TimeoutExpired):
|
||||
return False
|
||||
|
||||
|
||||
def setup_libreoffice_macro():
|
||||
macro_dir = os.path.expanduser(
|
||||
MACRO_DIR_MACOS if platform.system() == "Darwin" else MACRO_DIR_LINUX
|
||||
)
|
||||
macro_file = os.path.join(macro_dir, MACRO_FILENAME)
|
||||
|
||||
if (
|
||||
os.path.exists(macro_file)
|
||||
and "RecalculateAndSave" in Path(macro_file).read_text()
|
||||
):
|
||||
return True
|
||||
|
||||
if not os.path.exists(macro_dir):
|
||||
subprocess.run(
|
||||
["soffice", "--headless", "--terminate_after_init"],
|
||||
capture_output=True,
|
||||
timeout=10,
|
||||
env=get_soffice_env(),
|
||||
)
|
||||
os.makedirs(macro_dir, exist_ok=True)
|
||||
|
||||
try:
|
||||
Path(macro_file).write_text(RECALCULATE_MACRO)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def recalc(filename, timeout=30):
|
||||
if not Path(filename).exists():
|
||||
return {"error": f"File {filename} does not exist"}
|
||||
|
||||
abs_path = str(Path(filename).absolute())
|
||||
|
||||
if not setup_libreoffice_macro():
|
||||
return {"error": "Failed to setup LibreOffice macro"}
|
||||
|
||||
cmd = [
|
||||
"soffice",
|
||||
"--headless",
|
||||
"--norestore",
|
||||
"vnd.sun.star.script:Standard.Module1.RecalculateAndSave?language=Basic&location=application",
|
||||
abs_path,
|
||||
]
|
||||
|
||||
if platform.system() == "Linux":
|
||||
cmd = ["timeout", str(timeout)] + cmd
|
||||
elif platform.system() == "Darwin" and has_gtimeout():
|
||||
cmd = ["gtimeout", str(timeout)] + cmd
|
||||
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, env=get_soffice_env())
|
||||
|
||||
if result.returncode != 0 and result.returncode != 124:
|
||||
error_msg = result.stderr or "Unknown error during recalculation"
|
||||
if "Module1" in error_msg or "RecalculateAndSave" not in error_msg:
|
||||
return {"error": "LibreOffice macro not configured properly"}
|
||||
return {"error": error_msg}
|
||||
|
||||
try:
|
||||
wb = load_workbook(filename, data_only=True)
|
||||
|
||||
excel_errors = [
|
||||
"#VALUE!",
|
||||
"#DIV/0!",
|
||||
"#REF!",
|
||||
"#NAME?",
|
||||
"#NULL!",
|
||||
"#NUM!",
|
||||
"#N/A",
|
||||
]
|
||||
error_details = {err: [] for err in excel_errors}
|
||||
total_errors = 0
|
||||
|
||||
for sheet_name in wb.sheetnames:
|
||||
ws = wb[sheet_name]
|
||||
for row in ws.iter_rows():
|
||||
for cell in row:
|
||||
if cell.value is not None and isinstance(cell.value, str):
|
||||
for err in excel_errors:
|
||||
if err in cell.value:
|
||||
location = f"{sheet_name}!{cell.coordinate}"
|
||||
error_details[err].append(location)
|
||||
total_errors += 1
|
||||
break
|
||||
|
||||
wb.close()
|
||||
|
||||
result = {
|
||||
"status": "success" if total_errors == 0 else "errors_found",
|
||||
"total_errors": total_errors,
|
||||
"error_summary": {},
|
||||
}
|
||||
|
||||
for err_type, locations in error_details.items():
|
||||
if locations:
|
||||
result["error_summary"][err_type] = {
|
||||
"count": len(locations),
|
||||
"locations": locations[:20],
|
||||
}
|
||||
|
||||
wb_formulas = load_workbook(filename, data_only=False)
|
||||
formula_count = 0
|
||||
for sheet_name in wb_formulas.sheetnames:
|
||||
ws = wb_formulas[sheet_name]
|
||||
for row in ws.iter_rows():
|
||||
for cell in row:
|
||||
if (
|
||||
cell.value
|
||||
and isinstance(cell.value, str)
|
||||
and cell.value.startswith("=")
|
||||
):
|
||||
formula_count += 1
|
||||
wb_formulas.close()
|
||||
|
||||
result["total_formulas"] = formula_count
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
return {"error": str(e)}
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: python recalc.py <excel_file> [timeout_seconds]")
|
||||
print("\nRecalculates all formulas in an Excel file using LibreOffice")
|
||||
print("\nReturns JSON with error details:")
|
||||
print(" - status: 'success' or 'errors_found'")
|
||||
print(" - total_errors: Total number of Excel errors found")
|
||||
print(" - total_formulas: Number of formulas in the file")
|
||||
print(" - error_summary: Breakdown by error type with locations")
|
||||
print(" - #VALUE!, #DIV/0!, #REF!, #NAME?, #NULL!, #NUM!, #N/A")
|
||||
sys.exit(1)
|
||||
|
||||
filename = sys.argv[1]
|
||||
timeout = int(sys.argv[2]) if len(sys.argv) > 2 else 30
|
||||
|
||||
result = recalc(filename, timeout)
|
||||
print(json.dumps(result, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
76
.github/copilot-instructions.md
vendored
76
.github/copilot-instructions.md
vendored
@@ -1,76 +0,0 @@
|
||||
# OpenHIS — AI 编码助手 指南
|
||||
|
||||
目的:帮助自动化/AI 编码代理快速上手本仓库,包含架构要点、关键文件、常用构建/运行命令以及项目约定。请只按照仓库内真实可见的内容提出修改建议或补充说明。
|
||||
|
||||
- **代码组织**: 本项目是一个 Java 后端(多模块 Maven)+ Vue3 前端(Vite)的大型应用。
|
||||
- 后端主模块目录:`openhis-server-new/`(顶层为 `pom`,包含多个子模块)。关键子模块示例:`openhis-application`, `openhis-domain`, `openhis-common`, `core-*` 系列。
|
||||
- 前端目录:`openhis-ui-vue3/`(Vite + Vue 3,使用 Pinia、Element Plus 等)。
|
||||
|
||||
- **大局观(Big Picture)**: 后端以 Spring Boot(Java 17)实现,使用多模块 Maven 管理公共库与业务模块;前端由单独仓库目录通过 Vite 构建并以环境变量(`VITE_APP_BASE_API`)与后端交互。后端扫描 `com.core` 与 `com.openhis` 包(见 `OpenHisApplication.java`),启动类位于:`openhis-server-new/openhis-application/src/main/java/com/openhis/OpenHisApplication.java`。
|
||||
|
||||
- **运行/构建(Windows PowerShell 示例)**:
|
||||
- 构建后端(从仓库根执行):
|
||||
|
||||
```powershell
|
||||
cd openhis-server-new
|
||||
mvn clean package -DskipTests
|
||||
```
|
||||
|
||||
- 仅运行后端模块(开发时常用):
|
||||
|
||||
```powershell
|
||||
cd openhis-server-new/openhis-application
|
||||
mvn spring-boot:run
|
||||
# 或在 IDE 中运行 `com.openhis.OpenHisApplication` 的 main()
|
||||
```
|
||||
|
||||
- 前端启动与构建(需要 Node.js v16.x,仓库 README 建议 v16.15):
|
||||
|
||||
```powershell
|
||||
cd openhis-ui-vue3
|
||||
npm install
|
||||
npm run dev # 本地开发(热重载)
|
||||
npm run build:prod # 生产构建
|
||||
```
|
||||
|
||||
- **环境与配置**:
|
||||
- 后端配置:`openhis-server-new/openhis-application/src/main/resources/application.yml`(数据库、端口、profile 等)。README 还提及 `application-druid.yml`(若存在请优先查看)。
|
||||
- 前端配置:多个 `.env.*` 文件(例如 `.env.development`, `.env.staging`, `.env.production`),关键变量:`VITE_APP_BASE_API`(例如 `/dev-api`),前端通过 `import.meta.env.VITE_APP_BASE_API` 拼接后端 URL(见 `src/utils/request.js`、多个视图与组件)。
|
||||
|
||||
- **重要约定 / 模式**:
|
||||
- 后端采用 Java 17、Spring Boot 2.5.x 家族,父 POM在 `openhis-server-new/pom.xml` 定义。常用依赖版本在该 POM 的 `<properties>` 中集中维护。
|
||||
- 模块间以 Maven 模块依赖与 `com.core` / `com.openhis` 包名分层(见 `pom.xml` 的 `<modules>` 与 `dependencyManagement`)。
|
||||
- 前端通过 Vite 插件配置(`openhis-ui-vue3/vite/plugins`)管理 svg、自动导入等。UI 框架为 Element Plus,状态管理为 Pinia。
|
||||
|
||||
- **集成点 & 外部依赖**:
|
||||
- 数据库:PostgreSQL(README 建议 v16.2),仓库根含一个大型初始化 SQL:`数据库初始话脚本(请使用navicat16版本导入).sql`,用于初始化表与演示数据。
|
||||
- 缓存/会话:Redis(需自行配置)。
|
||||
- 其他:Flowable(工作流),Druid(连接池监控),第三方服务通过特定配置类(例如 `YbServiceConfig` 在 `OpenHisApplication` 中启用)。
|
||||
|
||||
- **调试与常见位置**:
|
||||
- 启动类:`openhis-server-new/openhis-application/src/main/java/com/openhis/OpenHisApplication.java`。
|
||||
- 全局配置:`openhis-server-new/openhis-application/src/main/resources/`(`application.yml`、profile 文件等)。
|
||||
- 前端入口:`openhis-ui-vue3/src/main.js`、路由在 `openhis-ui-vue3/src/router/index.js`。
|
||||
- API 文档与监控路径(通常由后端暴露并被前端访问):
|
||||
- Swagger UI: `<VITE_APP_BASE_API>/swagger-ui/index.html`(前端视图在 `src/views/tool/swagger/index.vue`)。
|
||||
- Druid: `<VITE_APP_BASE_API>/druid/login.html`(见前端相关视图引用)。
|
||||
|
||||
- **为 AI 代理的具体建议(如何安全、有效地修改代码)**:
|
||||
- 修改后端时:优先在子模块(例如 `openhis-application`)本地运行 `mvn spring-boot:run` 验证启动与基础 API;大量改动前先执行 `mvn -T1C -DskipTests clean package` 在 CI 环境上验证构建(本地机器也可用)。
|
||||
- 修改前端时:检查/调整对应 `.env.*` 文件中的 `VITE_APP_BASE_API`,使用 `npm run dev` 本地联调后端接口(可通过代理或将 `VITE_APP_BASE_API` 指向后端地址)。
|
||||
- 修改数据库结构或 seed:请参考仓库根的 SQL 初始化脚本,任何 DDL/数据变更需同步该脚本并通知数据库管理员/运维。
|
||||
|
||||
- **举例(常见任务示例)**:
|
||||
- 本地联调前端 + 后端(PowerShell):
|
||||
|
||||
```powershell
|
||||
# 启动后端
|
||||
cd openhis-server-new/openhis-application
|
||||
mvn spring-boot:run
|
||||
|
||||
# 启动前端(另开终端)
|
||||
cd openhis-ui-vue3
|
||||
npm run dev
|
||||
```
|
||||
|
||||
如需我把这些内容合并为更短或更详细的版本,或把其中某部分(例如后端模块依赖关系图、关键 Java 包说明)展开,请告诉我要增强哪一节。
|
||||
@@ -1,26 +0,0 @@
|
||||
# 修复门诊预约界面专家号查询结果显示问题
|
||||
|
||||
## 问题分析
|
||||
1. 前端传递的参数正确:`type=expert`,后端正确转换为`ticketType=专家`
|
||||
2. 实际查询返回了5条记录,但COUNT查询只返回了1条记录
|
||||
3. 这导致前端只显示了1条记录,而不是全部5条
|
||||
4. 原因:MyBatis-Plus自动生成的COUNT查询和实际查询使用了不同的条件,特别是逻辑删除条件
|
||||
|
||||
## 解决方案
|
||||
1. 修改TicketMapper.xml中的自定义COUNT查询,显式添加`delete_flag = '0'`条件
|
||||
2. 在selectTicketPage和selectTicketPage_mpCount查询中都添加逻辑删除条件
|
||||
3. 确保两个查询使用完全相同的WHERE条件
|
||||
|
||||
## 修复步骤
|
||||
1. 修改`selectTicketPage`查询,添加逻辑删除条件`and delete_flag = '0'`
|
||||
2. 修改`selectTicketPage_mpCount`查询,添加逻辑删除条件`and delete_flag = '0'`
|
||||
3. 确保两个查询的WHERE条件完全一致
|
||||
4. 测试修复后的功能,确保专家号能正确显示全部5条记录
|
||||
|
||||
## 代码修改点
|
||||
- 文件:`d:/work/openhis-server-new/openhis-domain/src/main/resources/mapper/clinical/TicketMapper.xml`
|
||||
- 查询:`selectTicketPage` 和 `selectTicketPage_mpCount`
|
||||
- 修改内容:添加逻辑删除条件`and delete_flag = '0'`
|
||||
|
||||
## 预期效果
|
||||
修复后,COUNT查询和实际查询将使用完全相同的条件,包括逻辑删除条件,从而确保COUNT查询返回正确的总记录数,前端能显示所有5条专家号记录。
|
||||
@@ -1,30 +0,0 @@
|
||||
# 修复门诊预约界面专家号查询COUNT结果不正确问题
|
||||
|
||||
## 问题分析
|
||||
1. 前端传递的参数正确:`type=expert`,后端正确转换为`ticketType=专家`
|
||||
2. COUNT查询和实际查询的WHERE条件完全相同:`WHERE delete_flag = '0' AND ticket_type = '专家'`
|
||||
3. 但COUNT查询只返回1条记录,而实际查询返回5条记录
|
||||
4. 原因:MyBatis-Plus的分页插件在处理自定义COUNT查询时,存在bug,导致COUNT查询结果不正确
|
||||
|
||||
## 解决方案
|
||||
修改`TicketAppServiceImpl.java`中的`listTicket`方法,不使用MyBatis-Plus的自动分页功能,而是手动实现分页查询:
|
||||
1. 直接调用`ticketService.countTickets`方法获取总记录数
|
||||
2. 手动构建查询条件
|
||||
3. 确保COUNT查询和实际查询使用完全相同的条件
|
||||
|
||||
## 修复步骤
|
||||
1. 修改`TicketAppServiceImpl.java`中的`listTicket`方法
|
||||
2. 手动实现分页查询,包括:
|
||||
- 构建查询条件
|
||||
- 调用`countTickets`获取总记录数
|
||||
- 调用`selectTicketList`获取分页数据
|
||||
- 手动组装分页结果
|
||||
3. 测试修复后的功能,确保专家号能正确显示全部5条记录
|
||||
|
||||
## 代码修改点
|
||||
- 文件:`d:/work/openhis-server-new/openhis-application/src/main/java/com/openhis/web/appointmentmanage/appservice/impl/TicketAppServiceImpl.java`
|
||||
- 方法:`listTicket`
|
||||
- 修改内容:替换MyBatis-Plus的自动分页,改为手动分页实现
|
||||
|
||||
## 预期效果
|
||||
修复后,COUNT查询和实际查询将使用完全相同的条件,COUNT查询将返回正确的总记录数(5条),前端能显示所有5条专家号记录。
|
||||
@@ -1,32 +0,0 @@
|
||||
## 问题分析
|
||||
根据日志和代码分析,发现号源列表显示"没有更多数据了"的问题原因:
|
||||
|
||||
1. **后端查询正常**:成功查询到5条符合条件的专家号源记录
|
||||
2. **数据转换失败**:在`convertToDto`方法中,`fee`字段类型转换错误
|
||||
3. **响应返回空列表**:由于转换异常,最终返回给前端的号源列表为空
|
||||
|
||||
## 问题根源
|
||||
- `Ticket`实体类的`fee`字段为**BigDecimal类型**(数据库存储)
|
||||
- `TicketDto`类的`fee`字段为**String类型**(前端展示)
|
||||
- 在`convertToDto`方法中,直接将BigDecimal类型的`fee`赋值给String类型的`fee`,导致**ClassCastException**
|
||||
|
||||
## 修复方案
|
||||
修改`TicketAppServiceImpl.java`文件中的`convertToDto`方法,将BigDecimal类型的`fee`转换为String类型:
|
||||
|
||||
```java
|
||||
// 原代码
|
||||
dto.setFee(ticket.getFee());
|
||||
|
||||
// 修复后代码
|
||||
dto.setFee(ticket.getFee().toString());
|
||||
```
|
||||
|
||||
## 预期效果
|
||||
1. 修复后,后端能成功将`Ticket`实体转换为`TicketDto`
|
||||
2. 前端能接收到包含5条专家号源的完整列表
|
||||
3. 页面显示正常,不再出现"没有更多数据了"的提示
|
||||
|
||||
## 验证方法
|
||||
1. 重新启动项目,访问号源管理页面
|
||||
2. 选择"专家号"类型,查看是否能正确显示5条号源记录
|
||||
3. 检查日志,确认没有类型转换异常
|
||||
@@ -1,23 +0,0 @@
|
||||
# 修复门诊预约界面专家号查询问题
|
||||
|
||||
## 问题分析
|
||||
从日志中发现关键问题:
|
||||
- 前端传递的ticket_type值是英文:`general` (普通号) 和 `expert` (专家号)
|
||||
- 数据库中存储的ticket_type值是中文:`普通` 和 `专家`
|
||||
- 导致查询条件不匹配,无法查询到数据
|
||||
|
||||
## 解决方案
|
||||
需要在后端添加类型映射转换,将前端传递的英文类型转换为数据库中存储的中文类型。
|
||||
|
||||
## 修复步骤
|
||||
1. 修改 `TicketAppServiceImpl.java` 文件,在处理type参数时添加映射转换逻辑
|
||||
2. 添加从英文类型到中文类型的映射关系
|
||||
3. 测试修复后的功能,确保普通号和专家号都能正确查询
|
||||
|
||||
## 代码修改点
|
||||
- 文件:`d:/work/openhis-server-new/openhis-application/src/main/java/com/openhis/web/appointmentmanage/appservice/impl/TicketAppServiceImpl.java`
|
||||
- 方法:`listTicket` 中的type参数处理部分
|
||||
- 修改内容:添加类型映射转换,将 "general" 转换为 "普通","expert" 转换为 "专家"
|
||||
|
||||
## 预期效果
|
||||
修复后,前端选择"普通号"或"专家号"时,系统能正确查询到对应的号源数据,不再出现"没有更多数据了"的提示。
|
||||
@@ -1,23 +0,0 @@
|
||||
**问题分析**:
|
||||
后端返回的响应格式是`{code: 200, msg: "操作成功", data: {total: 5, limit: 20, page: 1, list: [5条记录]}}`,而前端可能期望直接访问`list`属性,导致只能显示1条数据。
|
||||
|
||||
**修复方案**:
|
||||
|
||||
1. 修改`TicketAppServiceImpl.java`的`listTicket`方法,确保返回的分页数据格式正确
|
||||
2. 调整响应结构,使其更符合前端期望
|
||||
3. 保持与现有代码的兼容性
|
||||
|
||||
**修改点**:
|
||||
|
||||
* `TicketAppServiceImpl.java`:优化`listTicket`方法的响应格式
|
||||
|
||||
* 确保分页信息和列表数据都能正确返回给前端
|
||||
|
||||
**预期效果**:
|
||||
|
||||
* 后端返回正确格式的响应数据
|
||||
|
||||
* 前端能够正确显示所有5条专家号数据
|
||||
|
||||
* 保持与现有代码的兼容性
|
||||
|
||||
376
CODEBUDDY.md
376
CODEBUDDY.md
@@ -1,376 +0,0 @@
|
||||
# CODEBUDDY.md
|
||||
|
||||
This file provides guidance to CodeBuddy Code when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
This is a comprehensive Hospital Information System (HIS) built with a Java Spring Boot backend and Vue 3 frontend.
|
||||
|
||||
- **Backend**: Java 17, Spring Boot 2.5.15, multi-module Maven architecture
|
||||
- **Frontend**: Vue 3, Vite, Element Plus, Pinia state management
|
||||
- **Database**: PostgreSQL (recommended v16.2)
|
||||
- **Cache**: Redis
|
||||
|
||||
## Repository Structure
|
||||
|
||||
```
|
||||
.
|
||||
├── openhis-server-new/ # Backend multi-module Maven project
|
||||
│ ├── openhis-application/ # Main application module with startup class
|
||||
│ ├── openhis-domain/ # Business domain modules (administration, clinical, financial, etc.)
|
||||
│ ├── openhis-common/ # Shared utilities and common code
|
||||
│ ├── core-admin/ # Core administration module
|
||||
│ ├── core-framework/ # Framework configuration and security
|
||||
│ ├── core-system/ # System management module
|
||||
│ ├── core-quartz/ # Scheduled tasks
|
||||
│ ├── core-generator/ # Code generation utilities
|
||||
│ ├── core-common/ # Core utilities
|
||||
│ └── core-flowable/ # Workflow engine integration
|
||||
└── openhis-ui-vue3/ # Vue 3 frontend
|
||||
├── src/
|
||||
│ ├── api/ # API service layer
|
||||
│ ├── components/ # Reusable components
|
||||
│ ├── router/ # Vue Router configuration
|
||||
│ ├── store/ # Pinia state management
|
||||
│ ├── utils/ # Utility functions
|
||||
│ └── views/ # Page components
|
||||
└── vite/ # Vite plugins configuration
|
||||
```
|
||||
|
||||
## Build and Development Commands
|
||||
|
||||
### Backend (Java)
|
||||
|
||||
**Build the entire backend:**
|
||||
```bash
|
||||
cd openhis-server-new
|
||||
mvn clean package -DskipTests
|
||||
```
|
||||
|
||||
**Run the backend application (development):**
|
||||
```bash
|
||||
cd openhis-server-new/openhis-application
|
||||
mvn spring-boot:run
|
||||
```
|
||||
|
||||
**Alternative: Run directly from IDE:**
|
||||
- Run the main method in `openhis-server-new/openhis-application/src/main/java/com/openhis/OpenHisApplication.java`
|
||||
|
||||
**Start scripts:**
|
||||
- Linux/Mac: `openhis-server-new/start.sh`
|
||||
- Windows: `openhis-server-new/start.bat`
|
||||
|
||||
### Frontend (Vue 3)
|
||||
|
||||
**Install dependencies:**
|
||||
```bash
|
||||
cd openhis-ui-vue3
|
||||
npm install
|
||||
```
|
||||
|
||||
**Development server (with hot reload):**
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
- Runs on port 81 by default
|
||||
- Proxies `/dev-api` requests to `http://localhost:18080/openhis`
|
||||
|
||||
**Build for production:**
|
||||
```bash
|
||||
npm run build:prod # Production build
|
||||
npm run build:stage # Staging build
|
||||
npm run build:test # Test environment build
|
||||
npm run build:dev # Development build
|
||||
npm run build:spug # Spug environment build
|
||||
```
|
||||
|
||||
**Preview production build:**
|
||||
```bash
|
||||
npm run preview
|
||||
```
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
### Backend Architecture
|
||||
|
||||
The backend uses a multi-module Maven architecture with clear separation of concerns:
|
||||
|
||||
1. **openhis-application**: Entry point with `OpenHisApplication.java` (d:\his\openhis-server-new\openhis-application\src\main\java\com\openhis\OpenHisApplication.java:20)
|
||||
- Scans `com.core` and `com.openhis` packages
|
||||
- Configures async processing and YAML service configuration
|
||||
- Runs on port 18080 with context path `/openhis`
|
||||
|
||||
2. **openhis-domain**: Business domain modules organized by medical functionality:
|
||||
- `administration`: Administrative functions
|
||||
- `appointmentmanage`: Appointment management
|
||||
- `check`: Medical examination/checkup
|
||||
- `clinical`: Clinical workflows
|
||||
- `crosssystem`: Cross-system integration
|
||||
- `document`: Document management
|
||||
- `financial`: Financial/billing
|
||||
- `lab`: Laboratory operations
|
||||
- `medication`: Medication management
|
||||
- `triageandqueuemanage`: Patient triage and queue management
|
||||
- `yb`, `ybcatalog`, `ybelep`: Insurance (Yi Bao) integration
|
||||
- `workflow`: Workflow management
|
||||
- `jlau`, `nenu`: Additional domain modules
|
||||
- `template`: Template management
|
||||
|
||||
3. **Core Modules** (com.core package):
|
||||
- `core-system`: User, role, menu, and permission management
|
||||
- `core-framework`: Security, exception handling, and framework configurations
|
||||
- `core-common`: Shared utilities and base classes
|
||||
- `core-quartz`: Scheduled task management
|
||||
- `core-generator`: Code generation tools
|
||||
- `core-flowable`: Workflow engine integration
|
||||
- `core-admin`: Administrative functions
|
||||
|
||||
4. **openhis-common**: Domain-specific shared code and utilities under `com.openhis.common` package
|
||||
|
||||
**Key Technologies:**
|
||||
- MyBatis-Plus 3.5.5 for ORM with enhanced CRUD operations
|
||||
- Druid 1.2.27 connection pool with monitoring at `/druid/*`
|
||||
- Flowable 6.8.0 for workflow management
|
||||
- LiteFlow 2.12.4.1 for business rule orchestration
|
||||
- Swagger 3.0.0 for API documentation
|
||||
- JWT 0.9.1 for authentication
|
||||
- Hutool 5.3.8 utility library
|
||||
- Fastjson2 2.0.58 for JSON processing
|
||||
- Pinyin4j 2.5.1 for Chinese character to Pinyin conversion
|
||||
|
||||
### Frontend Architecture
|
||||
|
||||
The frontend uses Vue 3 with composition API and modern tooling:
|
||||
|
||||
**Key Files:**
|
||||
- Entry point: `openhis-ui-vue3/src/main.js`
|
||||
- Router configuration: `openhis-ui-vue3/src/router/index.js`
|
||||
- Store initialization: `openhis-ui-vue3/src/store/store.js`
|
||||
- Vite configuration: `openhis-ui-vue3/vite.config.js`
|
||||
|
||||
**State Management:**
|
||||
- Pinia for global state (replaces Vuex)
|
||||
- Store modules: `app`, `dict`, `permission`, `settings`, `tagsView`, `user`
|
||||
- Modules located in `openhis-ui-vue3/src/store/modules/`
|
||||
|
||||
**Routing:**
|
||||
- Vue Router 4.3.0
|
||||
- Two types of routes:
|
||||
- `constantRoutes`: Public routes (login, 404, etc.)
|
||||
- `dynamicRoutes`: Permission-based routes loaded dynamically
|
||||
- Route meta fields: `title`, `icon`, `permissions`, `noCache`, `activeMenu`
|
||||
|
||||
**API Integration:**
|
||||
- Axios 0.27.2 for HTTP requests
|
||||
- Base API URL configured via environment variables (`VITE_APP_BASE_API`)
|
||||
- Proxy configuration in vite.config.js for development
|
||||
- `/dev-api` → `http://localhost:18080/openhis`
|
||||
- `/ybplugin` → `http://localhost:5000` (insurance plugin)
|
||||
- Request/response interceptors in `openhis-ui-vue3/src/utils/request.js`
|
||||
- API service files organized by module in `openhis-ui-vue3/src/api/`
|
||||
- `administration`, `appoinmentmanage`, `monitor`, `system`, `tool`
|
||||
- Shared APIs: `home.js`, `login.js`, `menu.js`, `public.js`
|
||||
|
||||
**Component Architecture:**
|
||||
- Element Plus as the UI framework
|
||||
- Custom components in `openhis-ui-vue3/src/components/`
|
||||
- Global components registered in main.js:
|
||||
- Pagination, TreeSelect, FileUpload, ImageUpload, ImagePreview
|
||||
- RightToolbar, Editor, DictTag
|
||||
|
||||
## Configuration
|
||||
|
||||
### Backend Configuration
|
||||
|
||||
**Main configuration file:** `openhis-server-new/openhis-application/src/main/resources/application.yml`
|
||||
|
||||
**Environment-specific profiles:**
|
||||
- `application-dev.yml` - Development environment
|
||||
- `application-test.yml` - Test environment
|
||||
- `application-prd.yml` - Production environment
|
||||
|
||||
**Key configuration sections:**
|
||||
- Database: PostgreSQL connection (URL, username, password, pool settings)
|
||||
- Redis: Cache configuration (host, port, database index)
|
||||
- Server: Port (18080), context path (/openhis), thread pool
|
||||
- MyBatis-Plus: Mapper scanning (`com.core.**.domain,com.openhis.**.domain`), type aliases, logical delete
|
||||
- Logging: Debug levels for com.openhis and com.baomidou.mybatisplus
|
||||
- Swagger: API documentation at `/swagger-ui/index.html`
|
||||
- Druid: Database monitoring at `/druid/*` (credentials: openhis/123456)
|
||||
- Flowable: Workflow engine settings (schema update disabled)
|
||||
- LiteFlow: Business rule configuration at `config/flow.el.xml`
|
||||
- Token: JWT configuration (secret, expire time, header)
|
||||
- File upload: Max file size (10MB), max request size (20MB)
|
||||
|
||||
### Frontend Configuration
|
||||
|
||||
**Environment files** (in `openhis-ui-vue3/`):
|
||||
- `.env.dev` - Dev environment
|
||||
- `.env.development` - Development environment variables
|
||||
- `.env.staging` - Staging environment variables
|
||||
- `.env.production` - Production environment variables
|
||||
- `.env.test` - Test environment variables
|
||||
- `.env.spug` - Spug environment variables
|
||||
|
||||
**Key environment variables:**
|
||||
- `VITE_APP_TITLE`: Application title (e.g., "医院信息管理系统")
|
||||
- `VITE_APP_BASE_API`: Backend API base URL (e.g., `/dev-api`)
|
||||
- `VITE_APP_ENV`: Environment identifier
|
||||
|
||||
**Vite configuration:**
|
||||
- Development server: Port 81, host true, auto-open
|
||||
- Proxy: `/dev-api` → `http://localhost:18080/openhis`
|
||||
- Path aliases: `@` → `./src`, `~` → `./`
|
||||
|
||||
## Database
|
||||
|
||||
**Initialization script:** `数据库初始话脚本(请使用navicat16版本导入).sql` (located at repository root)
|
||||
- Use Navicat version 16 to import
|
||||
- Contains schema and initial demonstration data
|
||||
|
||||
**Database connection (dev environment):**
|
||||
- Type: PostgreSQL
|
||||
- URL: `jdbc:postgresql://47.116.196.11:15432/postgresql?currentSchema=hisdev`
|
||||
- Driver: `org.postgresql.Driver`
|
||||
- Schema: `hisdev`
|
||||
|
||||
## Common Development Tasks
|
||||
|
||||
### Running Full Stack Locally
|
||||
|
||||
**Terminal 1 - Start backend:**
|
||||
```bash
|
||||
cd openhis-server-new/openhis-application
|
||||
mvn spring-boot:run
|
||||
```
|
||||
|
||||
**Terminal 2 - Start frontend:**
|
||||
```bash
|
||||
cd openhis-ui-vue3
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Access the application at:
|
||||
- Frontend: http://localhost:81
|
||||
- Backend API: http://localhost:18080/openhis
|
||||
- Swagger UI: http://localhost:18080/openhis/swagger-ui/index.html
|
||||
- Druid monitoring: http://localhost:18080/openhis/druid/login.html
|
||||
|
||||
### Adding a New Backend Feature
|
||||
|
||||
1. Create domain entity in appropriate module under `openhis-domain/[module]/domain/`
|
||||
2. Create mapper interface in `openhis-domain/[module]/mapper/`
|
||||
3. Create mapper XML in `openhis-domain/[module]/resources/mapper/` (if custom SQL needed)
|
||||
4. Create service interface and implementation in `openhis-domain/[module]/service/`
|
||||
5. Create controller in `openhis-application/src/main/java/com/openhis/web/[module]/`
|
||||
6. Add MyBatis-Plus annotations if using enhanced features
|
||||
7. Test endpoints via Swagger UI at `http://localhost:18080/openhis/swagger-ui/index.html`
|
||||
|
||||
**Note:** Controllers are organized under `com.openhis.web` by business module (e.g., `web.administration`, `web.clinicalmanage`, `web.patientmanage`, etc.)
|
||||
|
||||
### Adding a New Frontend Page
|
||||
|
||||
1. Create Vue component in `openhis-ui-vue3/src/views/[module]/`
|
||||
2. Add API service methods in `openhis-ui-vue3/src/api/`
|
||||
3. Add route to `openhis-ui-vue3/src/router/index.js` (constantRoutes or dynamicRoutes)
|
||||
4. Add Pinia store module if state management needed
|
||||
5. Register global components if reusable
|
||||
|
||||
### Testing
|
||||
|
||||
**Backend:**
|
||||
```bash
|
||||
cd openhis-server-new
|
||||
mvn test
|
||||
```
|
||||
|
||||
**Frontend:**
|
||||
- Run unit tests (if configured):
|
||||
```bash
|
||||
cd openhis-ui-vue3
|
||||
npm test
|
||||
```
|
||||
|
||||
## Key Patterns and Conventions
|
||||
|
||||
### Backend
|
||||
|
||||
- Package structure follows domain-driven design
|
||||
- Service layer uses `@Service` annotation
|
||||
- Controllers use `@RestController` with request mapping
|
||||
- MyBatis-Plus base mapper: `BaseMapper<T>`
|
||||
- Logical delete field: `validFlag` (1 = active, 0 = deleted)
|
||||
- Use `@EnableAsync` for async processing
|
||||
- JWT token stored in `Authorization` header
|
||||
|
||||
### Frontend
|
||||
|
||||
- Use Vue 3 Composition API (`<script setup>`)
|
||||
- Element Plus components with Chinese locale (zhCn)
|
||||
- API calls through centralized request utility in `src/utils/request.js`
|
||||
- Route-based permission control
|
||||
- Dictionary data through `useDict()` composable
|
||||
- Global properties: `$download`, `$downloadGet`, `$parseTime`, `$resetForm`, `$handleTree`, `$formatDateStr`
|
||||
- CSS in SCSS with global styles in `src/assets/styles/index.scss`
|
||||
- Registered global components: DictTag, Pagination, TreeSelect, FileUpload, ImageUpload, ImagePreview, RightToolbar, Editor
|
||||
- Hiprint plugin for printing functionality (window.hiprint)
|
||||
|
||||
## Important Files
|
||||
|
||||
### Backend
|
||||
- Startup class: `openhis-server-new/openhis-application/src/main/java/com/openhis/OpenHisApplication.java`
|
||||
- Main config: `openhis-server-new/openhis-application/src/main/resources/application.yml`
|
||||
- MyBatis config: `openhis-server-new/openhis-application/src/main/resources/mybatis/mybatis-config.xml`
|
||||
- Parent POM: `openhis-server-new/pom.xml`
|
||||
|
||||
### Frontend
|
||||
- Entry point: `openhis-ui-vue3/src/main.js`
|
||||
- Router: `openhis-ui-vue3/src/router/index.js`
|
||||
- Request utils: `openhis-ui-vue3/src/utils/request.js`
|
||||
- Vite config: `openhis-ui-vue3/vite.config.js`
|
||||
- Environment files: `openhis-ui-vue3/.env.*`
|
||||
|
||||
## External Integrations
|
||||
|
||||
- **PostgreSQL 42.2.27**: Primary database
|
||||
- **MySQL Connector 9.4.0**: MySQL database support (alternative)
|
||||
- **Redis**: Caching and session management
|
||||
- **Flowable 6.8.0**: Workflow engine
|
||||
- **LiteFlow 2.12.4.1**: Business rule engine
|
||||
- **Swagger 3.0.0**: API documentation
|
||||
- **Druid 1.2.27**: Database connection pool and monitoring
|
||||
- **Element Plus 2.12.0**: Vue 3 UI component library
|
||||
- **Pinia 2.2.0**: State management
|
||||
- **Vite 5.0.4**: Build tool and dev server
|
||||
- **Hutool 5.3.8**: Java utility library
|
||||
- **Fastjson2 2.0.58**: JSON processing
|
||||
- **Pinyin4j 2.5.1**: Chinese character to Pinyin conversion
|
||||
- **iText 5.5.12**: PDF generation
|
||||
- **Apache POI 4.1.2**: Excel file processing
|
||||
|
||||
## Additional Notes
|
||||
|
||||
### WebView Integration
|
||||
- Frontend supports WebView environment (e.g., embedded in desktop applications)
|
||||
- Chrome WebView integration with C# accessor (`chrome.webview.hostObjects.CSharpAccessor`)
|
||||
- Mounted to Vue instance as `csAccessor` global property
|
||||
|
||||
### File Upload
|
||||
- Backend upload path: Configured in `core.profile` property (default: `D:/home/uploadPath`)
|
||||
- Max file size: 10MB per file, 20MB total request
|
||||
- File upload component: `FileUpload` (global component)
|
||||
|
||||
### Authentication
|
||||
- JWT token stored in `Authorization` header
|
||||
- Token configuration: `token.secret`, `token.expireTime`, `token.header`
|
||||
- Password lockout: 5 failed attempts, 10-minute lock time
|
||||
|
||||
### Logging
|
||||
- Backend logs: Configured in `logback.xml`
|
||||
- Debug logging enabled for: `com.openhis`, `com.baomidou.mybatisplus`, `com.alibaba.druid`
|
||||
- Druid slow SQL threshold: 1000ms
|
||||
|
||||
### Code Generation
|
||||
- Backend code generator: `core-generator` module
|
||||
- Access via Swagger or `/tool/gen` route
|
||||
- Uses Velocity templates in `openhis-application/src/main/resources/vm/`
|
||||
674
LICENSE
674
LICENSE
@@ -1,674 +0,0 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright 2022-2025 湖北天天数链技术有限公司
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
OpenHis Copyright (C) 2022-2025 湖北天天数链技术有限公司
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
@@ -1,223 +0,0 @@
|
||||
# MyBatis-Plus 自动填充处理器优化指南
|
||||
|
||||
## 概述
|
||||
本文档说明如何优化 `MybastisColumnsHandler` 以确保所有实体的审计字段(create_by、create_time、update_by、update_time)能够正确自动填充。
|
||||
|
||||
## 问题背景
|
||||
在 OpenHIS 系统中,当保存实体时可能会遇到以下错误:
|
||||
```
|
||||
org.postgresql.util.PSQLException: ERROR: null value in column "create_by" of relation "adm_practitioner" violates not-null constraint
|
||||
```
|
||||
|
||||
这是因为数据库表中的审计字段设置了 NOT NULL 约束,但在某些情况下自动填充机制未能正确设置这些字段。
|
||||
|
||||
## 解决方案
|
||||
通过优化 `MybastisColumnsHandler` 来确保总是使用当前登录用户的用户名填充 `create_by` 字段,使用当前时间填充 `create_time` 字段。
|
||||
|
||||
## 实施步骤
|
||||
|
||||
### 1. 替换现有处理器
|
||||
将 `D:\his\openhis-server-new\core-framework\src\main\java\com\core\framework\handler\MybastisColumnsHandler.java` 文件替换为以下内容:
|
||||
|
||||
```java
|
||||
package com.core.framework.handler;
|
||||
|
||||
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
||||
import com.core.common.core.domain.model.LoginUser;
|
||||
import com.core.common.utils.SecurityUtils;
|
||||
import com.core.framework.config.TenantContext;
|
||||
import org.apache.ibatis.reflection.MetaObject;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* MyBatis-Plus 自动填充处理器
|
||||
* 用于自动填充创建时间和更新时间,以及创建人和更新人
|
||||
*/
|
||||
@Component
|
||||
public class MybastisColumnsHandler implements MetaObjectHandler {
|
||||
|
||||
// 设置数据新增时的字段自动赋值规则
|
||||
@Override
|
||||
public void insertFill(MetaObject metaObject) {
|
||||
// 填充创建时间
|
||||
Date currentTime = new Date();
|
||||
this.strictInsertFill(metaObject, "createTime", Date.class, currentTime);
|
||||
this.strictInsertFill(metaObject, "create_time", Date.class, currentTime);
|
||||
|
||||
// 获取当前登录用户名
|
||||
String username = getCurrentUsername();
|
||||
|
||||
// 填充创建人
|
||||
this.strictInsertFill(metaObject, "createBy", String.class, username);
|
||||
this.strictInsertFill(metaObject, "create_by", String.class, username);
|
||||
|
||||
// 确保tenantId被设置
|
||||
Integer tenantId = getCurrentTenantId();
|
||||
if (tenantId == null) {
|
||||
throw new RuntimeException("无法获取当前租户ID,请确保用户已登录或正确设置租户上下文");
|
||||
}
|
||||
this.strictInsertFill(metaObject, "tenantId", Integer.class, tenantId);
|
||||
this.strictInsertFill(metaObject, "tenant_id", Integer.class, tenantId);
|
||||
}
|
||||
|
||||
// 设置数据修改时的字段自动赋值规则
|
||||
@Override
|
||||
public void updateFill(MetaObject metaObject) {
|
||||
// 填充更新时间
|
||||
Date currentTime = new Date();
|
||||
this.strictUpdateFill(metaObject, "updateTime", Date.class, currentTime);
|
||||
this.strictUpdateFill(metaObject, "update_time", Date.class, currentTime);
|
||||
|
||||
// 填充更新人
|
||||
String username = getCurrentUsername();
|
||||
this.strictUpdateFill(metaObject, "updateBy", String.class, username);
|
||||
this.strictUpdateFill(metaObject, "update_by", String.class, username);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前登录用户名
|
||||
* @return 当前登录用户名,如果无法获取则返回 "system"
|
||||
*/
|
||||
private String getCurrentUsername() {
|
||||
String username = "system"; // 默认值
|
||||
|
||||
try {
|
||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||
if (loginUser != null) {
|
||||
username = loginUser.getUsername();
|
||||
} else {
|
||||
// 尝试从请求中获取用户信息
|
||||
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||
if (attributes != null) {
|
||||
HttpServletRequest request = attributes.getRequest();
|
||||
// 可以在这里添加额外的逻辑来从请求中获取用户信息
|
||||
// 例如从请求头、session等获取用户信息
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 记录异常但不中断处理流程
|
||||
System.err.println("获取当前登录用户时发生异常: " + e.getMessage());
|
||||
// 可以考虑记录日志
|
||||
}
|
||||
|
||||
return username;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前租户 ID
|
||||
*/
|
||||
private Integer getCurrentTenantId() {
|
||||
Integer result = null;
|
||||
|
||||
// 首先尝试从线程局部变量中获取租户ID(适用于定时任务等场景)
|
||||
Integer threadLocalTenantId = TenantContext.getCurrentTenant();
|
||||
if (threadLocalTenantId != null) {
|
||||
result = threadLocalTenantId;
|
||||
} else {
|
||||
// 获取当前登录用户的租户ID(优先使用SecurityUtils中储存的LoginUser的租户ID)
|
||||
try {
|
||||
if (SecurityUtils.getAuthentication() != null) {
|
||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||
if (loginUser != null) {
|
||||
result = loginUser.getTenantId();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 记录异常但不中断处理
|
||||
System.err.println("获取当前登录用户租户ID时发生异常: " + e.getMessage());
|
||||
}
|
||||
|
||||
if (result == null) {
|
||||
// 尝试从请求头中获取租户ID
|
||||
ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
|
||||
if (attributes != null) {
|
||||
HttpServletRequest request = attributes.getRequest();
|
||||
if (request != null) {
|
||||
// 从请求头获取租户ID,假设header名称为"X-Tenant-ID" ; 登录接口前端把租户id放到请求头里
|
||||
String tenantIdHeader = request.getHeader("X-Tenant-ID");
|
||||
String requestMethodName = request.getHeader("Request-Method-Name");
|
||||
// 登录
|
||||
if ("login".equals(requestMethodName)) {
|
||||
if (tenantIdHeader != null && !tenantIdHeader.isEmpty()) {
|
||||
try {
|
||||
result = Integer.parseInt(tenantIdHeader);
|
||||
} catch (NumberFormatException e) {
|
||||
System.err.println("解析请求头中的租户ID时发生异常: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果仍然没有获取到租户ID,返回默认值
|
||||
if (result == null) {
|
||||
System.out.println("警告: 未能获取当前租户ID,将使用默认租户ID 1");
|
||||
result = 1; // 默认租户ID
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 验证处理器是否被正确扫描
|
||||
确保在主应用类或配置类中启用了自动填充功能:
|
||||
|
||||
```java
|
||||
@SpringBootApplication
|
||||
@MapperScan("com.openhis.*.mapper") // 确保扫描到你的mapper
|
||||
@EnableTransactionManagement // 启用事务管理
|
||||
public class OpenHisApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(OpenHisApplication.class, args);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 测试验证
|
||||
创建一个简单的测试来验证自动填充是否正常工作:
|
||||
|
||||
```java
|
||||
@SpringBootTest
|
||||
public class AuditFieldTest {
|
||||
@Autowired
|
||||
private PractitionerMapper practitionerMapper;
|
||||
|
||||
@Test
|
||||
public void testAuditFieldsAutoFill() {
|
||||
Practitioner practitioner = new Practitioner();
|
||||
practitioner.setName("Test Practitioner");
|
||||
|
||||
// 保存实体
|
||||
practitionerMapper.insert(practitioner);
|
||||
|
||||
// 验证审计字段是否被正确填充
|
||||
assertThat(practitioner.getCreateBy()).isNotNull();
|
||||
assertThat(practitioner.getCreateBy()).isNotEqualTo("");
|
||||
assertThat(practitioner.getCreateTime()).isNotNull();
|
||||
|
||||
// 清理测试数据
|
||||
practitionerMapper.deleteById(practitioner.getId());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **安全上下文**:确保在调用保存方法时用户已登录,这样 `SecurityUtils.getLoginUser()` 才能返回有效的用户对象。
|
||||
|
||||
2. **异常处理**:处理器中包含了异常处理,如果无法获取当前用户,将使用 "system" 作为默认值。
|
||||
|
||||
3. **租户ID**:处理器也处理租户ID的自动填充,这对于多租户系统很重要。
|
||||
|
||||
4. **兼容性**:处理器同时支持驼峰命名(createBy)和下划线命名(create_by)的字段,以兼容不同的配置。
|
||||
|
||||
## 总结
|
||||
通过优化 `MybastisColumnsHandler`,我们可以确保所有实体在保存时都能正确填充审计字段,避免因缺少这些字段而引发的数据库约束错误,同时保持数据完整性和审计跟踪功能。
|
||||
184
QWEN.md
184
QWEN.md
@@ -1,184 +0,0 @@
|
||||
# Qwen Code Context for HIS (Hospital Information System)
|
||||
|
||||
## Project Overview
|
||||
|
||||
This is a comprehensive Hospital Information System (HIS) called OpenHIS, built with a Java Spring Boot backend and Vue 3 frontend. The system is designed to manage hospital operations including patient management, appointments, clinical workflows, billing, and administrative tasks.
|
||||
|
||||
### Technology Stack
|
||||
|
||||
**Backend:**
|
||||
- Java 17
|
||||
- Spring Boot 2.5.15
|
||||
- PostgreSQL (recommended v16.2)
|
||||
- Redis
|
||||
- MyBatis-Plus 3.5.5 for ORM
|
||||
- Druid 1.2.27 for database connection pooling
|
||||
- Flowable 6.8.0 for workflow management
|
||||
- LiteFlow 2.12.4.1 for business rule orchestration
|
||||
- Swagger 3.0.0 for API documentation
|
||||
- JWT 0.9.1 for authentication
|
||||
|
||||
**Frontend:**
|
||||
- Vue 3 with Composition API
|
||||
- Vite 5.0.4 as build tool
|
||||
- Element Plus 2.12.0 as UI component library
|
||||
- Pinia 2.2.0 for state management
|
||||
- Axios 0.27.2 for HTTP requests
|
||||
- Sass for styling
|
||||
|
||||
## Repository Structure
|
||||
|
||||
```
|
||||
.
|
||||
├── openhis-server-new/ # Backend multi-module Maven project
|
||||
│ ├── openhis-application/ # Main application module with startup class
|
||||
│ ├── openhis-domain/ # Business domain modules (administration, clinical, financial, etc.)
|
||||
│ ├── openhis-common/ # Shared utilities and common code
|
||||
│ ├── core-admin/ # Core administration module
|
||||
│ ├── core-framework/ # Framework configuration and security
|
||||
│ ├── core-system/ # System management module
|
||||
│ ├── core-quartz/ # Scheduled tasks
|
||||
│ ├── core-generator/ # Code generation utilities
|
||||
│ ├── core-common/ # Core utilities
|
||||
│ └── core-flowable/ # Workflow engine integration
|
||||
├── openhis-ui-vue3/ # Vue 3 frontend
|
||||
│ ├── src/
|
||||
│ │ ├── api/ # API service layer
|
||||
│ │ ├── components/ # Reusable components
|
||||
│ │ ├── router/ # Vue Router configuration
|
||||
│ │ ├── store/ # Pinia state management
|
||||
│ │ ├── utils/ # Utility functions
|
||||
│ │ └── views/ # Page components
|
||||
│ └── vite/ # Vite plugins configuration
|
||||
├── sql/ # Database scripts
|
||||
├── 发版记录/ # Release records
|
||||
└── 迁移记录-DB变更记录/ # Database migration records
|
||||
```
|
||||
|
||||
## Building and Running
|
||||
|
||||
### Backend Setup
|
||||
|
||||
1. **Prerequisites:**
|
||||
- JDK 17 (required)
|
||||
- PostgreSQL v16.2 (required)
|
||||
- Redis (stable version)
|
||||
|
||||
2. **Database Setup:**
|
||||
- Import the database initialization script using Navicat 16 or later
|
||||
- Script location: `sql/20251224init脚本(使用Navicat Premium 17导入).sql`
|
||||
- Configure database connection in `application.yml` or `application-dev.yml`
|
||||
|
||||
3. **Build and Run:**
|
||||
```bash
|
||||
cd openhis-server-new
|
||||
mvn clean package -DskipTests
|
||||
cd openhis-application
|
||||
mvn spring-boot:run
|
||||
```
|
||||
|
||||
Or run directly from IDE by executing `OpenHisApplication.java`
|
||||
|
||||
### Frontend Setup
|
||||
|
||||
1. **Prerequisites:**
|
||||
- Node.js v16.15 (recommended)
|
||||
|
||||
2. **Installation and Run:**
|
||||
```bash
|
||||
cd openhis-ui-vue3
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
3. **Access the application:**
|
||||
- Frontend: http://localhost:81
|
||||
- Backend API: http://localhost:18080/openhis
|
||||
- Swagger UI: http://localhost:18080/openhis/swagger-ui/index.html
|
||||
|
||||
## Development Conventions
|
||||
|
||||
### Backend Architecture
|
||||
|
||||
The backend follows a multi-module Maven architecture with clear separation of concerns:
|
||||
|
||||
1. **openhis-application**: Entry point with `OpenHisApplication.java`
|
||||
- Scans `com.core` and `com.openhis` packages
|
||||
- Configured to run on port 18080 with context path `/openhis`
|
||||
|
||||
2. **openhis-domain**: Business domain modules organized by medical functionality:
|
||||
- `administration`: Administrative functions
|
||||
- `appointmentmanage`: Appointment management
|
||||
- `check`: Medical examination/checkup
|
||||
- `clinical`: Clinical workflows
|
||||
- `crosssystem`: Cross-system integration
|
||||
- `document`: Document management
|
||||
- `financial`: Financial/billing
|
||||
- `lab`: Laboratory operations
|
||||
- `medication`: Medication management
|
||||
- `triageandqueuemanage`: Patient triage and queue management
|
||||
- `yb`, `ybcatalog`, `ybelep`: Insurance (Yi Bao) integration
|
||||
- `workflow`: Workflow management
|
||||
|
||||
3. **Core Modules** (com.core package):
|
||||
- `core-system`: User, role, menu, and permission management
|
||||
- `core-framework`: Security, exception handling, and framework configurations
|
||||
- `core-common`: Shared utilities and base classes
|
||||
- `core-quartz`: Scheduled task management
|
||||
- `core-generator`: Code generation tools
|
||||
- `core-flowable`: Workflow engine integration
|
||||
- `core-admin`: Administrative functions
|
||||
|
||||
### Frontend Architecture
|
||||
|
||||
The frontend uses Vue 3 with composition API and modern tooling:
|
||||
|
||||
1. **State Management:** Pinia for global state with modules for app, dict, permission, settings, tagsView, and user
|
||||
|
||||
2. **Routing:** Vue Router 4.3.0 with public routes and dynamic permission-based routes
|
||||
|
||||
3. **API Integration:** Axios with request/response interceptors and API services organized by module
|
||||
|
||||
4. **Component Architecture:** Element Plus as UI framework with custom components in `src/components/`
|
||||
|
||||
## Key Configuration Files
|
||||
|
||||
### Backend Configuration
|
||||
|
||||
- Main config: `openhis-server-new/openhis-application/src/main/resources/application.yml`
|
||||
- Environment-specific: `application-dev.yml`, `application-test.yml`, `application-prd.yml`
|
||||
- Database connection settings, Redis configuration, server settings, and MyBatis-Plus configuration
|
||||
|
||||
### Frontend Configuration
|
||||
|
||||
- Environment files: `.env.*` in `openhis-ui-vue3/`
|
||||
- Vite configuration: `vite.config.js`
|
||||
- Main entry: `src/main.js`
|
||||
- Router: `src/router/index.js`
|
||||
|
||||
## Common Development Tasks
|
||||
|
||||
### Adding a New Backend Feature
|
||||
|
||||
1. Create domain entity in appropriate module under `openhis-domain/[module]/domain/`
|
||||
2. Create mapper interface in `openhis-domain/[module]/mapper/`
|
||||
3. Create service interface and implementation in `openhis-domain/[module]/service/`
|
||||
4. Create controller in `openhis-application/src/main/java/com/openhis/web/[module]/`
|
||||
5. Test endpoints via Swagger UI
|
||||
|
||||
### Adding a New Frontend Page
|
||||
|
||||
1. Create Vue component in `openhis-ui-vue3/src/views/[module]/`
|
||||
2. Add API service methods in `openhis-ui-vue3/src/api/`
|
||||
3. Add route to `openhis-ui-vue3/src/router/index.js`
|
||||
4. Add Pinia store module if state management needed
|
||||
|
||||
## Important Notes
|
||||
|
||||
- The system uses logical deletion with a `validFlag` field (1 = active, 0 = deleted)
|
||||
- JWT tokens are stored in the `Authorization` header
|
||||
- The system supports WebView environments with C# accessor integration
|
||||
- File uploads are configured with max 10MB per file and 20MB total request size
|
||||
- Password lockout occurs after 5 failed attempts with a 10-minute lock time
|
||||
- The system includes a code generator accessible via `/tool/gen` route
|
||||
- Printing functionality is implemented using the hiprint plugin
|
||||
@@ -1,13 +0,0 @@
|
||||
-- 添加缺失的字段到 sys_user_config 表
|
||||
|
||||
-- 添加 create_by 字段
|
||||
ALTER TABLE sys_user_config ADD COLUMN IF NOT EXISTS create_by VARCHAR(64) DEFAULT NULL COMMENT '创建者';
|
||||
|
||||
-- 添加 create_time 字段
|
||||
ALTER TABLE sys_user_config ADD COLUMN IF NOT EXISTS create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间';
|
||||
|
||||
-- 添加 update_by 字段
|
||||
ALTER TABLE sys_user_config ADD COLUMN IF NOT EXISTS update_by VARCHAR(64) DEFAULT NULL COMMENT '更新者';
|
||||
|
||||
-- 添加 update_time 字段
|
||||
ALTER TABLE sys_user_config ADD COLUMN IF NOT EXISTS update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间';
|
||||
@@ -1,39 +0,0 @@
|
||||
-- 添加缺失的字段到 sys_user_config 表 (PostgreSQL 版本)
|
||||
|
||||
-- 添加 create_by 字段
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name='sys_user_config' AND column_name='create_by') THEN
|
||||
ALTER TABLE sys_user_config ADD COLUMN create_by VARCHAR(64) DEFAULT NULL;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- 添加 create_time 字段
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name='sys_user_config' AND column_name='create_time') THEN
|
||||
ALTER TABLE sys_user_config ADD COLUMN create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP;
|
||||
COMMENT ON COLUMN sys_user_config.create_time IS '创建时间';
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- 添加 update_by 字段
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name='sys_user_config' AND column_name='update_by') THEN
|
||||
ALTER TABLE sys_user_config ADD COLUMN update_by VARCHAR(64) DEFAULT NULL;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- 添加 update_time 字段
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name='sys_user_config' AND column_name='update_time') THEN
|
||||
ALTER TABLE sys_user_config ADD COLUMN update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP;
|
||||
COMMENT ON COLUMN sys_user_config.update_time IS '更新时间';
|
||||
END IF;
|
||||
END $$;
|
||||
@@ -1,202 +0,0 @@
|
||||
# OpenHIS 系统审计字段填充最佳实践
|
||||
|
||||
## 概述
|
||||
本文档介绍如何在 OpenHIS 系统中确保所有实体的审计字段(create_by、create_time、update_by、update_time)能够正确自动填充。
|
||||
|
||||
## 自动填充机制
|
||||
|
||||
### 1. 基础实体类
|
||||
所有需要审计字段的实体类都应该继承自 `HisBaseEntity`:
|
||||
|
||||
```java
|
||||
import com.core.common.core.domain.HisBaseEntity;
|
||||
|
||||
@Data
|
||||
@TableName("adm_practitioner")
|
||||
public class Practitioner extends HisBaseEntity {
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
private String name;
|
||||
|
||||
// 其他业务字段...
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 自动填充处理器
|
||||
系统使用 `MybastisColumnsHandler` 来自动填充审计字段:
|
||||
|
||||
```java
|
||||
@Component
|
||||
public class MybastisColumnsHandler implements MetaObjectHandler {
|
||||
@Override
|
||||
public void insertFill(MetaObject metaObject) {
|
||||
// 填充创建时间和创建人
|
||||
this.strictInsertFill(metaObject, "createTime", Date.class, new Date());
|
||||
this.strictInsertFill(metaObject, "create_time", Date.class, new Date());
|
||||
|
||||
String username = getCurrentUsername(); // 获取当前用户名
|
||||
this.strictInsertFill(metaObject, "createBy", String.class, username);
|
||||
this.strictInsertFill(metaObject, "create_by", String.class, username);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFill(MetaObject metaObject) {
|
||||
// 填充更新时间和更新人
|
||||
this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());
|
||||
this.strictUpdateFill(metaObject, "update_time", Date.class, new Date());
|
||||
|
||||
String username = getCurrentUsername(); // 获取当前用户名
|
||||
this.strictUpdateFill(metaObject, "updateBy", String.class, username);
|
||||
this.strictUpdateFill(metaObject, "update_by", String.class, username);
|
||||
}
|
||||
|
||||
private String getCurrentUsername() {
|
||||
String username = "system";
|
||||
try {
|
||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||
if (loginUser != null) {
|
||||
username = loginUser.getUsername();
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
return username;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 确保自动填充正常工作的要点
|
||||
|
||||
### 1. 检查实体类继承关系
|
||||
确保所有实体类都正确继承了 `HisBaseEntity`:
|
||||
|
||||
```java
|
||||
// 正确的做法
|
||||
public class Practitioner extends HisBaseEntity { ... }
|
||||
|
||||
// 如果不能继承 HisBaseEntity,则需要手动添加审计字段
|
||||
public class CustomEntity {
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private String createBy;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Date createTime;
|
||||
|
||||
@TableField(fill = FieldFill.UPDATE)
|
||||
private String updateBy;
|
||||
|
||||
@TableField(fill = FieldFill.UPDATE)
|
||||
private Date updateTime;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 验证安全上下文
|
||||
确保在执行数据库操作时有有效的安全上下文:
|
||||
|
||||
```java
|
||||
@Service
|
||||
public class PractitionerService {
|
||||
public void savePractitioner(Practitioner practitioner) {
|
||||
// 确保调用此方法时用户已登录
|
||||
// SecurityUtils.getLoginUser() 应该能返回有效的 LoginUser 对象
|
||||
|
||||
// MyBatis-Plus 会在保存时自动调用 MybastisColumnsHandler
|
||||
practitionerMapper.insert(practitioner);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 检查配置
|
||||
确保自动填充处理器被正确配置:
|
||||
|
||||
```yaml
|
||||
# application.yml
|
||||
mybatis-plus:
|
||||
global-config:
|
||||
db-config:
|
||||
# 其他配置...
|
||||
configuration:
|
||||
# 其他配置...
|
||||
```
|
||||
|
||||
### 4. 手动填充(特殊情况)
|
||||
在某些特殊情况下,如果自动填充不工作,可以手动设置:
|
||||
|
||||
```java
|
||||
@Service
|
||||
public class PractitionerService {
|
||||
public void savePractitionerManually(Practitioner practitioner) {
|
||||
// 手动设置审计字段
|
||||
Date now = new Date();
|
||||
String currentUser = getCurrentUsername();
|
||||
|
||||
practitioner.setCreateTime(now);
|
||||
practitioner.setCreateBy(currentUser);
|
||||
practitioner.setUpdateTime(now);
|
||||
practitioner.setUpdateBy(currentUser);
|
||||
|
||||
practitionerMapper.insert(practitioner);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 常见问题及解决方案
|
||||
|
||||
### 问题1:自动填充不生效
|
||||
**原因:**
|
||||
- 实体类没有继承 `HisBaseEntity`
|
||||
- `MybastisColumnsHandler` 没有被Spring管理(缺少@Component注解)
|
||||
- 没有有效的安全上下文
|
||||
|
||||
**解决方案:**
|
||||
- 确保实体类继承 `HisBaseEntity`
|
||||
- 检查 `MybastisColumnsHandler` 是否有 `@Component` 注解
|
||||
- 确保在调用保存方法时用户已登录
|
||||
|
||||
### 问题2:获取不到当前用户
|
||||
**原因:**
|
||||
- 用户未登录
|
||||
- 安全上下文配置错误
|
||||
|
||||
**解决方案:**
|
||||
- 在调用保存方法前确保用户已登录
|
||||
- 检查安全配置是否正确
|
||||
|
||||
### 问题3:批量操作时审计字段未填充
|
||||
**原因:**
|
||||
- 批量操作可能绕过了自动填充机制
|
||||
|
||||
**解决方案:**
|
||||
- 对于批量操作,手动设置审计字段
|
||||
- 或者使用 MyBatis-Plus 的批量操作方法,确保它们支持自动填充
|
||||
|
||||
## 测试验证
|
||||
|
||||
创建一个简单的测试来验证自动填充是否正常工作:
|
||||
|
||||
```java
|
||||
@SpringBootTest
|
||||
public class AuditFieldTest {
|
||||
@Autowired
|
||||
private PractitionerMapper practitionerMapper;
|
||||
|
||||
@Test
|
||||
public void testAuditFieldsAutoFill() {
|
||||
Practitioner practitioner = new Practitioner();
|
||||
practitioner.setName("Test Practitioner");
|
||||
|
||||
// 保存实体
|
||||
practitionerMapper.insert(practitioner);
|
||||
|
||||
// 验证审计字段是否被正确填充
|
||||
assertThat(practitioner.getCreateBy()).isNotNull();
|
||||
assertThat(practitioner.getCreateTime()).isNotNull();
|
||||
|
||||
// 清理测试数据
|
||||
practitionerMapper.deleteById(practitioner.getId());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 总结
|
||||
通过遵循以上最佳实践,可以确保 OpenHIS 系统中的所有实体在保存时都能正确填充审计字段,避免因缺少这些字段而引发的数据库约束错误。
|
||||
@@ -1,113 +0,0 @@
|
||||
# 关于数据库审计字段(create_by, create_time等)的处理方案
|
||||
|
||||
## 问题描述
|
||||
在使用OpenHIS系统时,可能会遇到如下错误:
|
||||
```
|
||||
org.postgresql.util.PSQLException: ERROR: null value in column "create_by" of relation "adm_practitioner" violates not-null constraint
|
||||
```
|
||||
|
||||
## 问题分析
|
||||
1. 数据库表中的审计字段(如create_by, create_time)设置了NOT NULL约束
|
||||
2. 应用程序层面使用了MyBatis-Plus的自动填充功能来设置这些字段
|
||||
3. 当自动填充机制失效时,就会出现违反非空约束的错误
|
||||
|
||||
## 解决方案
|
||||
|
||||
### 方案一:修复自动填充机制(推荐)
|
||||
系统已经实现了自动填充机制,位于 `MybastisColumnsHandler.java`:
|
||||
|
||||
```java
|
||||
// 设置数据新增时候的,字段自动赋值规则
|
||||
@Override
|
||||
public void insertFill(MetaObject metaObject) {
|
||||
// 同时填充驼峰和下划线命名的字段,以兼容不同的配置
|
||||
this.strictInsertFill(metaObject, "createTime", Date.class, new Date());
|
||||
this.strictInsertFill(metaObject, "create_time", Date.class, new Date());
|
||||
|
||||
String username = "system";
|
||||
try {
|
||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||
if (loginUser != null) {
|
||||
username = loginUser.getUsername();
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
// 使用 fillStrategy 确保即使字段为 null 也会被填充
|
||||
this.strictInsertFill(metaObject, "createBy", String.class, username);
|
||||
this.strictInsertFill(metaObject, "create_by", String.class, username);
|
||||
// 如果 strictInsertFill 没有生效,使用 setFieldValByName 强制设置
|
||||
if (metaObject.hasGetter("createBy") && metaObject.getValue("createBy") == null) {
|
||||
this.setFieldValByName("createBy", username, metaObject);
|
||||
}
|
||||
if (metaObject.hasGetter("create_by") && metaObject.getValue("create_by") == null) {
|
||||
this.setFieldValByName("create_by", username, metaObject);
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
确保所有实体类都继承自 `HisBaseEntity` 或 `BaseEntity`,这样就能自动获得审计字段。
|
||||
|
||||
### 方案二:移除数据库约束(谨慎使用)
|
||||
如果确实需要允许审计字段为NULL,可以移除数据库约束:
|
||||
|
||||
```sql
|
||||
-- 移除 adm_practitioner 表中 create_by 列的 NOT NULL 约束
|
||||
ALTER TABLE "public"."adm_practitioner"
|
||||
ALTER COLUMN "create_by" DROP NOT NULL;
|
||||
|
||||
-- 同样处理 create_time 列(如果需要)
|
||||
ALTER TABLE "public"."adm_practitioner"
|
||||
ALTER COLUMN "create_time" DROP NOT NULL;
|
||||
```
|
||||
|
||||
### 方案三:批量修复所有表的约束
|
||||
如果多个表都存在这个问题,可以使用以下脚本:
|
||||
|
||||
```sql
|
||||
-- 为所有表的审计字段移除NOT NULL约束
|
||||
-- 注意:执行前请备份数据库!
|
||||
|
||||
-- 1. 检查所有包含审计字段的表
|
||||
SELECT
|
||||
table_name,
|
||||
column_name,
|
||||
is_nullable
|
||||
FROM
|
||||
information_schema.columns
|
||||
WHERE
|
||||
column_name IN ('create_by', 'create_time', 'update_by', 'update_time')
|
||||
AND table_schema = 'public'
|
||||
AND is_nullable = 'NO'; -- NO 表示 NOT NULL 约束
|
||||
|
||||
-- 2. 根据需要移除特定表的约束
|
||||
-- 示例:移除多个表的create_by约束
|
||||
ALTER TABLE "public"."adm_practitioner" ALTER COLUMN "create_by" DROP NOT NULL;
|
||||
ALTER TABLE "public"."adm_patient" ALTER COLUMN "create_by" DROP NOT NULL;
|
||||
-- 添加更多表的处理...
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 确保实体类继承基础类
|
||||
所有实体类应继承 `HisBaseEntity` 或 `BaseEntity`:
|
||||
|
||||
```java
|
||||
@Data
|
||||
@TableName("adm_practitioner")
|
||||
public class Practitioner extends HisBaseEntity {
|
||||
// 其他字段...
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 检查安全上下文
|
||||
确保在保存数据时有有效的安全上下文,这样自动填充处理器才能获取到当前用户信息。
|
||||
|
||||
### 3. 验证自动填充配置
|
||||
确保 `MybastisColumnsHandler` 在Spring容器中被正确注册(使用@Component注解)。
|
||||
|
||||
## 总结
|
||||
- 推荐保持数据库中的NOT NULL约束,确保数据完整性
|
||||
- 依赖MyBatis-Plus的自动填充机制来设置审计字段
|
||||
- 确保所有实体类继承基础实体类
|
||||
- 在必要时才考虑移除数据库约束
|
||||
@@ -1,104 +0,0 @@
|
||||
-- 检查流水号(display_order)是否按“科室+医生+当天”正确递增
|
||||
--
|
||||
-- 说明:
|
||||
-- 1. display_order 存的是纯数字(1, 2, 3...),不带时间戳前缀
|
||||
-- 2. 时间戳前缀(如 20260109)是在前端显示时加上的
|
||||
-- 3. 后端用 Redis key "ORG-{科室ID}-DOC-{医生ID}" 按天自增
|
||||
--
|
||||
-- 如何判断逻辑是否正确:
|
||||
-- 同一科室、同一医生、同一天的记录,display_order 应该递增(1, 2, 3...)
|
||||
-- 不同科室、不同医生、不同天的记录,可能都是 1(这是正常的)
|
||||
|
||||
-- ========================================
|
||||
-- 查询1:按“科室+医生+日期”分组,看每组内的 display_order 是否递增
|
||||
-- ========================================
|
||||
SELECT
|
||||
DATE(start_time) AS 日期,
|
||||
organization_id AS 科室ID,
|
||||
registrar_id AS 医生ID,
|
||||
COUNT(*) AS 该组记录数,
|
||||
MIN(display_order) AS 最小序号,
|
||||
MAX(display_order) AS 最大序号,
|
||||
STRING_AGG(display_order::text, ', ' ORDER BY start_time) AS 序号列表,
|
||||
STRING_AGG(id::text, ', ' ORDER BY start_time) AS 记录ID列表
|
||||
FROM adm_encounter
|
||||
WHERE delete_flag = '0'
|
||||
AND start_time >= CURRENT_DATE - INTERVAL '7 days' -- 只看最近7天
|
||||
AND display_order IS NOT NULL
|
||||
GROUP BY DATE(start_time), organization_id, registrar_id
|
||||
ORDER BY 日期 DESC, 科室ID, 医生ID;
|
||||
|
||||
-- ========================================
|
||||
-- 查询2:详细查看每条记录,看同组内的序号是否连续
|
||||
-- ========================================
|
||||
SELECT
|
||||
id AS 记录ID,
|
||||
DATE(start_time) AS 日期,
|
||||
organization_id AS 科室ID,
|
||||
registrar_id AS 医生ID,
|
||||
start_time AS 挂号时间,
|
||||
display_order AS 流水号,
|
||||
-- 计算:同组内的序号应该是 1, 2, 3...,看是否有重复或跳号
|
||||
ROW_NUMBER() OVER (
|
||||
PARTITION BY DATE(start_time), organization_id, registrar_id
|
||||
ORDER BY start_time
|
||||
) AS 应该是第几个,
|
||||
CASE
|
||||
WHEN display_order = ROW_NUMBER() OVER (
|
||||
PARTITION BY DATE(start_time), organization_id, registrar_id
|
||||
ORDER BY start_time
|
||||
) THEN '✓ 正常'
|
||||
ELSE '✗ 异常'
|
||||
END AS 是否正常
|
||||
FROM adm_encounter
|
||||
WHERE delete_flag = '0'
|
||||
AND start_time >= CURRENT_DATE - INTERVAL '7 days'
|
||||
AND display_order IS NOT NULL
|
||||
ORDER BY DATE(start_time) DESC, organization_id, registrar_id, start_time;
|
||||
|
||||
-- ========================================
|
||||
-- 查询3:只看今天的数据(最直观)
|
||||
-- ========================================
|
||||
SELECT
|
||||
id AS 记录ID,
|
||||
organization_id AS 科室ID,
|
||||
registrar_id AS 医生ID,
|
||||
start_time AS 挂号时间,
|
||||
display_order AS 流水号
|
||||
FROM adm_encounter
|
||||
WHERE delete_flag = '0'
|
||||
AND DATE(start_time) = CURRENT_DATE
|
||||
AND display_order IS NOT NULL
|
||||
ORDER BY organization_id, registrar_id, start_time;
|
||||
|
||||
-- ========================================
|
||||
-- 查询4:发现问题 - 找出同组内 display_order 重复的记录
|
||||
-- ========================================
|
||||
WITH ranked AS (
|
||||
SELECT
|
||||
id,
|
||||
DATE(start_time) AS reg_date,
|
||||
organization_id,
|
||||
registrar_id,
|
||||
start_time,
|
||||
display_order,
|
||||
ROW_NUMBER() OVER (
|
||||
PARTITION BY DATE(start_time), organization_id, registrar_id
|
||||
ORDER BY start_time
|
||||
) AS should_be_order
|
||||
FROM adm_encounter
|
||||
WHERE delete_flag = '0'
|
||||
AND start_time >= CURRENT_DATE - INTERVAL '7 days'
|
||||
AND display_order IS NOT NULL
|
||||
)
|
||||
SELECT
|
||||
reg_date AS 日期,
|
||||
organization_id AS 科室ID,
|
||||
registrar_id AS 医生ID,
|
||||
COUNT(*) AS 重复数量,
|
||||
STRING_AGG(id::text || '->' || display_order::text, ', ') AS 问题记录
|
||||
FROM ranked
|
||||
WHERE display_order != should_be_order
|
||||
GROUP BY reg_date, organization_id, registrar_id
|
||||
ORDER BY reg_date DESC;
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
-- 会诊邀请对象表
|
||||
CREATE TABLE hisdev.consultation_invited (
|
||||
id BIGINT NOT NULL PRIMARY KEY,
|
||||
consultation_request_id BIGINT NOT NULL,
|
||||
invited_department_id BIGINT,
|
||||
invited_department_name VARCHAR(100),
|
||||
invited_physician_id BIGINT,
|
||||
invited_physician_name VARCHAR(50),
|
||||
invited_status SMALLINT DEFAULT 0,
|
||||
confirm_time TIMESTAMP,
|
||||
confirm_opinion TEXT,
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
create_by VARCHAR(64),
|
||||
update_by VARCHAR(64),
|
||||
tenant_id BIGINT,
|
||||
is_deleted SMALLINT DEFAULT 0
|
||||
);
|
||||
|
||||
-- 添加注释
|
||||
COMMENT ON TABLE hisdev.consultation_invited IS '会诊邀请对象表';
|
||||
COMMENT ON COLUMN hisdev.consultation_invited.id IS '主键ID';
|
||||
COMMENT ON COLUMN hisdev.consultation_invited.consultation_request_id IS '会诊申请ID(外键)';
|
||||
COMMENT ON COLUMN hisdev.consultation_invited.invited_department_id IS '邀请科室ID';
|
||||
COMMENT ON COLUMN hisdev.consultation_invited.invited_department_name IS '邀请科室名称';
|
||||
COMMENT ON COLUMN hisdev.consultation_invited.invited_physician_id IS '邀请医生ID';
|
||||
COMMENT ON COLUMN hisdev.consultation_invited.invited_physician_name IS '邀请医生姓名';
|
||||
COMMENT ON COLUMN hisdev.consultation_invited.invited_status IS '邀请状态(0-待确认,1-已确认,2-已拒绝)';
|
||||
COMMENT ON COLUMN hisdev.consultation_invited.confirm_time IS '确认时间';
|
||||
COMMENT ON COLUMN hisdev.consultation_invited.confirm_opinion IS '确认意见';
|
||||
COMMENT ON COLUMN hisdev.consultation_invited.create_time IS '创建时间';
|
||||
COMMENT ON COLUMN hisdev.consultation_invited.update_time IS '更新时间';
|
||||
COMMENT ON COLUMN hisdev.consultation_invited.create_by IS '创建人';
|
||||
COMMENT ON COLUMN hisdev.consultation_invited.update_by IS '更新人';
|
||||
COMMENT ON COLUMN hisdev.consultation_invited.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN hisdev.consultation_invited.is_deleted IS '删除标识(0-未删除,1-已删除)';
|
||||
|
||||
-- 创建索引
|
||||
CREATE INDEX idx_consultation_request_id ON hisdev.consultation_invited(consultation_request_id);
|
||||
CREATE INDEX idx_invited_physician_id ON hisdev.consultation_invited(invited_physician_id);
|
||||
CREATE INDEX idx_tenant_id ON hisdev.consultation_invited(tenant_id);
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
# 检查后端API返回数据结构
|
||||
|
||||
## 问题分析
|
||||
尽管我们更新了DTO和SQL查询,前端仍然没有显示创建时间,可能的原因:
|
||||
1. API响应中没有包含createTime字段
|
||||
2. SQL查询没有正确返回createTime字段
|
||||
3. 数据库中createTime字段本身为null
|
||||
4. JSON序列化问题
|
||||
|
||||
## 检查步骤
|
||||
|
||||
### 1. 检查数据库中数据
|
||||
首先检查数据库中sys_user表的createTime字段是否正确填充:
|
||||
```sql
|
||||
SELECT user_id, user_name, nick_name, create_time
|
||||
FROM sys_user
|
||||
WHERE create_time IS NOT NULL
|
||||
LIMIT 10;
|
||||
```
|
||||
|
||||
### 2. 检查API端点
|
||||
API端点是:GET /base-data-manage/practitioner/user-practitioner-page
|
||||
这个端点在PractitionerController中定义,调用practitionerAppService.getUserPractitionerPage()
|
||||
|
||||
### 3. 检查SQL查询
|
||||
在PractitionerAppMapper.xml中,我们已经添加了createTime字段:
|
||||
```xml
|
||||
T2.create_time
|
||||
```
|
||||
|
||||
### 4. 验证DTO映射
|
||||
UserAndPractitionerDto中已添加createTime字段:
|
||||
```java
|
||||
private Date createTime;
|
||||
```
|
||||
|
||||
### 5. 检查JSON序列化
|
||||
检查是否有@JsonFormat注解或其他序列化配置问题
|
||||
@@ -1,290 +0,0 @@
|
||||
# 深度排查 MyBatis-Plus 自动填充不生效问题
|
||||
|
||||
## 问题概述
|
||||
尽管对 MyBatis-Plus 的自动填充处理器进行了多次优化和配置,但 `create_by` 和 `create_time` 字段仍然没有被自动填充。
|
||||
|
||||
## 深度排查步骤
|
||||
|
||||
### 1. 检查 AOP 代理是否生效
|
||||
MyBatis-Plus 的自动填充功能依赖于 AOP 代理。如果实体类的方法被直接调用而非通过代理调用,自动填充可能不会生效。
|
||||
|
||||
### 2. 验证 Service 层实现
|
||||
确保使用的是 MyBatis-Plus 提供的通用 Service 方法,而不是自定义的 SQL。
|
||||
|
||||
### 3. 检查 @TableField 注解配置
|
||||
确认实体类中的字段注解配置正确。
|
||||
|
||||
### 4. 检查事务配置
|
||||
某些事务配置可能会影响 AOP 代理的生效。
|
||||
|
||||
## 解决方案
|
||||
|
||||
### 方案一:在 Service 层手动设置审计字段
|
||||
|
||||
创建一个工具类来统一处理审计字段的设置:
|
||||
|
||||
```java
|
||||
@Component
|
||||
public class AuditFieldUtil {
|
||||
|
||||
public static void setCreateInfo(Object entity) {
|
||||
if (entity == null) return;
|
||||
|
||||
try {
|
||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||
String username = loginUser != null ? loginUser.getUsername() : "system";
|
||||
Date currentTime = new Date();
|
||||
|
||||
// 使用反射设置字段值
|
||||
Field createByField = getField(entity.getClass(), "createBy");
|
||||
if (createByField != null) {
|
||||
createByField.setAccessible(true);
|
||||
if (createByField.get(entity) == null || "".equals(createByField.get(entity))) {
|
||||
createByField.set(entity, username);
|
||||
}
|
||||
}
|
||||
|
||||
Field createTimeField = getField(entity.getClass(), "createTime");
|
||||
if (createTimeField != null) {
|
||||
createTimeField.setAccessible(true);
|
||||
if (createTimeField.get(entity) == null) {
|
||||
createTimeField.set(entity, currentTime);
|
||||
}
|
||||
}
|
||||
|
||||
// 处理下划线命名的字段
|
||||
Field createByFieldUnderscore = getField(entity.getClass(), "create_by");
|
||||
if (createByFieldUnderscore != null) {
|
||||
createByFieldUnderscore.setAccessible(true);
|
||||
if (createByFieldUnderscore.get(entity) == null || "".equals(createByFieldUnderscore.get(entity))) {
|
||||
createByFieldUnderscore.set(entity, username);
|
||||
}
|
||||
}
|
||||
|
||||
Field createTimeFieldUnderscore = getField(entity.getClass(), "create_time");
|
||||
if (createTimeFieldUnderscore != null) {
|
||||
createTimeFieldUnderscore.setAccessible(true);
|
||||
if (createTimeFieldUnderscore.get(entity) == null) {
|
||||
createTimeFieldUnderscore.set(entity, currentTime);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("设置审计字段时发生异常: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static Field getField(Class<?> clazz, String fieldName) {
|
||||
try {
|
||||
return clazz.getDeclaredField(fieldName);
|
||||
} catch (NoSuchFieldException e) {
|
||||
if (clazz.getSuperclass() != null) {
|
||||
return getField(clazz.getSuperclass(), fieldName);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
然后在 Service 实现中使用:
|
||||
|
||||
```java
|
||||
@Service
|
||||
public class PractitionerServiceImpl extends ServiceImpl<PractitionerMapper, Practitioner>
|
||||
implements IPractitionerService {
|
||||
|
||||
@Autowired
|
||||
private AuditFieldUtil auditFieldUtil;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public boolean save(Practitioner entity) {
|
||||
// 在保存前手动设置审计字段
|
||||
auditFieldUtil.setCreateInfo(entity);
|
||||
return super.save(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public boolean saveBatch(Collection<Practitioner> entityList) {
|
||||
entityList.forEach(auditFieldUtil::setCreateInfo);
|
||||
return super.saveBatch(entityList);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 方案二:重写 BaseMapper 方法
|
||||
|
||||
如果 Service 层的方法不起作用,可以直接在 Mapper 层处理:
|
||||
|
||||
```java
|
||||
@Mapper
|
||||
public interface PractitionerMapper extends BaseMapper<Practitioner> {
|
||||
|
||||
@Insert({
|
||||
"<script>",
|
||||
"INSERT INTO adm_practitioner (",
|
||||
"id, active_flag, name, name_json, gender_enum, birth_date, deceased_date,",
|
||||
"phone, address, address_province, address_city, address_district, address_street,",
|
||||
"address_json, py_str, wb_str, bus_no, yb_no, user_id, tenant_id, delete_flag,",
|
||||
"create_by, create_time, update_by, update_time, org_id,",
|
||||
"phar_prac_cert_no, prsc_dr_cert_code, dr_profttl_code, kpd_code, signature, pos_no",
|
||||
") VALUES (",
|
||||
"#{id}, #{activeFlag}, #{name}, #{nameJson}, #{genderEnum}, #{birthDate}, #{deceasedDate},",
|
||||
"#{phone}, #{address}, #{addressProvince}, #{addressCity}, #{addressDistrict}, #{addressStreet},",
|
||||
"#{addressJson}, #{pyStr}, #{wbStr}, #{busNo}, #{ybNo}, #{userId}, #{tenantId}, #{deleteFlag},",
|
||||
"#{createBy}, #{createTime}, #{updateBy}, #{updateTime}, #{orgId},",
|
||||
"#{pharPracCertNo}, #{prscDrCertCode}, #{drProfttlCode}, #{kpdCode}, #{signature}, #{posNo}",
|
||||
")",
|
||||
"</script>"
|
||||
})
|
||||
@Options(useGeneratedKeys = true, keyProperty = "id")
|
||||
int insertWithAuditFields(Practitioner record);
|
||||
}
|
||||
```
|
||||
|
||||
### 方案三:使用 MyBatis 拦截器
|
||||
|
||||
创建一个 MyBatis 拦截器来自动填充字段:
|
||||
|
||||
```java
|
||||
@Intercepts({
|
||||
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
|
||||
})
|
||||
@Component
|
||||
public class AuditFieldInterceptor implements Interceptor {
|
||||
|
||||
@Override
|
||||
public Object intercept(Invocation invocation) throws Throwable {
|
||||
Object[] args = invocation.getArgs();
|
||||
MappedStatement ms = (MappedStatement) args[0];
|
||||
Object parameter = args[1];
|
||||
|
||||
String sqlCommandType = ms.getSqlCommandType().toString();
|
||||
|
||||
if ("INSERT".equals(sqlCommandType)) {
|
||||
setCreateAuditFields(parameter);
|
||||
} else if ("UPDATE".equals(sqlCommandType)) {
|
||||
setUpdateAuditFields(parameter);
|
||||
}
|
||||
|
||||
return invocation.proceed();
|
||||
}
|
||||
|
||||
private void setCreateAuditFields(Object parameter) {
|
||||
if (parameter == null) return;
|
||||
|
||||
try {
|
||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||
String username = loginUser != null ? loginUser.getUsername() : "system";
|
||||
Date currentTime = new Date();
|
||||
|
||||
// 设置 createBy 和 createTime
|
||||
setFieldValue(parameter, "createBy", username);
|
||||
setFieldValue(parameter, "create_time", username);
|
||||
setFieldValue(parameter, "createTime", currentTime);
|
||||
setFieldValue(parameter, "create_time", currentTime);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void setUpdateAuditFields(Object parameter) {
|
||||
if (parameter == null) return;
|
||||
|
||||
try {
|
||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||
String username = loginUser != null ? loginUser.getUsername() : "system";
|
||||
Date currentTime = new Date();
|
||||
|
||||
// 设置 updateBy 和 updateTime
|
||||
setFieldValue(parameter, "updateBy", username);
|
||||
setFieldValue(parameter, "update_by", username);
|
||||
setFieldValue(parameter, "updateTime", currentTime);
|
||||
setFieldValue(parameter, "update_time", currentTime);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void setFieldValue(Object obj, String fieldName, Object value) {
|
||||
try {
|
||||
Field field = getField(obj.getClass(), fieldName);
|
||||
if (field != null) {
|
||||
field.setAccessible(true);
|
||||
if (field.get(obj) == null) { // 只在原值为 null 时设置
|
||||
field.set(obj, value);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 忽略无法设置的字段
|
||||
}
|
||||
}
|
||||
|
||||
private Field getField(Class<?> clazz, String fieldName) {
|
||||
try {
|
||||
return clazz.getDeclaredField(fieldName);
|
||||
} catch (NoSuchFieldException e) {
|
||||
if (clazz.getSuperclass() != null) {
|
||||
return getField(clazz.getSuperclass(), fieldName);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object plugin(Object target) {
|
||||
if (target instanceof Executor) {
|
||||
return Plugin.wrap(target, this);
|
||||
} else {
|
||||
return target;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProperties(Properties properties) {}
|
||||
}
|
||||
```
|
||||
|
||||
## 推荐实施顺序
|
||||
|
||||
1. 首先尝试方案一(Service 层手动设置),这是最简单且可控的方式
|
||||
2. 如果方案一不行,尝试方案三(MyBatis 拦截器),它在更底层起作用
|
||||
3. 方案二是最后的选择,需要重写具体的插入逻辑
|
||||
|
||||
## 验证方法
|
||||
|
||||
创建一个测试来验证自动填充是否生效:
|
||||
|
||||
```java
|
||||
@SpringBootTest
|
||||
public class AuditFieldTest {
|
||||
|
||||
@Autowired
|
||||
private IPractitionerService practitionerService;
|
||||
|
||||
@Test
|
||||
public void testAuditFieldFill() {
|
||||
Practitioner practitioner = new Practitioner();
|
||||
practitioner.setName("Test Practitioner");
|
||||
|
||||
// 记录保存前的值
|
||||
System.out.println("保存前 - createBy: " + practitioner.getCreateBy());
|
||||
System.out.println("保存前 - createTime: " + practitioner.getCreateTime());
|
||||
|
||||
boolean success = practitionerService.save(practitioner);
|
||||
|
||||
// 从数据库重新查询以验证
|
||||
Practitioner saved = practitionerService.getById(practitioner.getId());
|
||||
System.out.println("保存后 - createBy: " + saved.getCreateBy());
|
||||
System.out.println("保存后 - createTime: " + saved.getCreateTime());
|
||||
|
||||
Assertions.assertTrue(success);
|
||||
Assertions.assertNotNull(saved.getCreateBy());
|
||||
Assertions.assertNotNull(saved.getCreateTime());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
通过这些方案,应该能够解决自动填充不生效的问题。
|
||||
@@ -1,143 +0,0 @@
|
||||
# 诊断 MyBatis-Plus 自动填充问题
|
||||
|
||||
## 问题现象
|
||||
尽管 `MybastisColumnsHandler` 已经实现并配置了自动填充功能,但 `create_by` 和 `create_time` 字段仍然没有被正确填充。
|
||||
|
||||
## 可能的原因及解决方案
|
||||
|
||||
### 1. 检查组件扫描配置
|
||||
确保 `MybastisColumnsHandler` 类被Spring容器正确管理:
|
||||
|
||||
```java
|
||||
@Component // 确保这个注解存在
|
||||
public class MybastisColumnsHandler implements MetaObjectHandler {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 检查包扫描路径
|
||||
在主应用类中确保扫描到了处理器所在的包:
|
||||
|
||||
```java
|
||||
@SpringBootApplication
|
||||
@MapperScan("com.openhis.*.mapper") // 确保扫描到你的mapper
|
||||
@ComponentScan(basePackages = {"com.core", "com.openhis"}) // 确保扫描到处理器
|
||||
public class OpenHisApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(OpenHisApplication.class, args);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 验证实体类配置
|
||||
确保实体类正确继承了 `HisBaseEntity` 并且字段上有正确的注解:
|
||||
|
||||
```java
|
||||
@Data
|
||||
@TableName("adm_practitioner")
|
||||
public class Practitioner extends HisBaseEntity {
|
||||
// 不需要在子类中重复定义 createBy, createTime 等字段
|
||||
// 因为它们已在 HisBaseEntity 中定义并带有 @TableField(fill = FieldFill.INSERT)
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 检查安全上下文
|
||||
自动填充处理器依赖于安全上下文来获取当前用户。确保在执行保存操作时用户已登录:
|
||||
|
||||
```java
|
||||
// 在保存之前,确保用户已登录
|
||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||
if (loginUser == null) {
|
||||
// 用户未登录,可能需要手动设置审计字段
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 手动测试自动填充
|
||||
创建一个简单的测试来验证自动填充是否正常工作:
|
||||
|
||||
```java
|
||||
@SpringBootTest
|
||||
public class AutoFillTest {
|
||||
|
||||
@Autowired
|
||||
private PractitionerMapper practitionerMapper;
|
||||
|
||||
@Test
|
||||
public void testAutoFill() {
|
||||
Practitioner practitioner = new Practitioner();
|
||||
practitioner.setName("Test Practitioner");
|
||||
|
||||
// 检查在保存前字段是否为空
|
||||
System.out.println("Before insert - createBy: " + practitioner.getCreateBy());
|
||||
System.out.println("Before insert - createTime: " + practitioner.getCreateTime());
|
||||
|
||||
// 执行插入操作
|
||||
int result = practitionerMapper.insert(practitioner);
|
||||
|
||||
// 检查保存后字段是否被填充
|
||||
System.out.println("After insert - createBy: " + practitioner.getCreateBy());
|
||||
System.out.println("After insert - createTime: " + practitioner.getCreateTime());
|
||||
|
||||
assertThat(result).isEqualTo(1);
|
||||
assertThat(practitioner.getCreateBy()).isNotNull();
|
||||
assertThat(practitioner.getCreateTime()).isNotNull();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 6. 临时解决方案
|
||||
如果自动填充仍然不工作,可以在服务层手动设置这些字段:
|
||||
|
||||
```java
|
||||
@Service
|
||||
public class PractitionerServiceImpl extends ServiceImpl<PractitionerMapper, Practitioner>
|
||||
implements IPractitionerService {
|
||||
|
||||
@Override
|
||||
public void savePractitioner(Practitioner practitioner) {
|
||||
// 手动设置审计字段
|
||||
if (practitioner.getCreateBy() == null || practitioner.getCreateBy().isEmpty()) {
|
||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||
if (loginUser != null) {
|
||||
practitioner.setCreateBy(loginUser.getUsername());
|
||||
} else {
|
||||
practitioner.setCreateBy("system"); // 默认值
|
||||
}
|
||||
}
|
||||
|
||||
if (practitioner.getCreateTime() == null) {
|
||||
practitioner.setCreateTime(new Date());
|
||||
}
|
||||
|
||||
// 执行保存操作
|
||||
this.save(practitioner);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 7. 检查 MyBatis-Plus 版本兼容性
|
||||
确保使用的 MyBatis-Plus 版本与自动填充功能兼容。当前项目使用的是 3.5.5 版本,应该支持自动填充功能。
|
||||
|
||||
### 8. 调试自动填充处理器
|
||||
在 `MybastisColumnsHandler` 中添加日志来调试是否被调用:
|
||||
|
||||
```java
|
||||
@Override
|
||||
public void insertFill(MetaObject metaObject) {
|
||||
System.out.println("MybastisColumnsHandler.insertFill() called"); // 调试日志
|
||||
|
||||
Date currentTime = new Date();
|
||||
this.strictInsertFill(metaObject, "createTime", Date.class, currentTime);
|
||||
this.strictInsertFill(metaObject, "create_time", Date.class, currentTime);
|
||||
|
||||
String username = getCurrentUsername();
|
||||
System.out.println("Setting createBy to: " + username); // 调试日志
|
||||
|
||||
this.strictInsertFill(metaObject, "createBy", String.class, username);
|
||||
this.strictInsertFill(metaObject, "create_by", String.class, username);
|
||||
|
||||
// ... 其他代码
|
||||
}
|
||||
```
|
||||
|
||||
通过以上步骤,应该能够诊断并解决自动填充不工作的问题。
|
||||
@@ -1,145 +0,0 @@
|
||||
package com.core.framework.handler;
|
||||
|
||||
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
||||
import com.core.common.core.domain.model.LoginUser;
|
||||
import com.core.common.utils.SecurityUtils;
|
||||
import com.core.framework.config.TenantContext;
|
||||
import org.apache.ibatis.reflection.MetaObject;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* MyBatis-Plus 自动填充处理器
|
||||
* 用于自动填充创建时间和更新时间,以及创建人和更新人
|
||||
*/
|
||||
@Component
|
||||
public class MybastisColumnsHandler implements MetaObjectHandler {
|
||||
|
||||
// 设置数据新增时的字段自动赋值规则
|
||||
@Override
|
||||
public void insertFill(MetaObject metaObject) {
|
||||
// 填充创建时间
|
||||
Date currentTime = new Date();
|
||||
this.strictInsertFill(metaObject, "createTime", Date.class, currentTime);
|
||||
this.strictInsertFill(metaObject, "create_time", Date.class, currentTime);
|
||||
|
||||
// 获取当前登录用户名
|
||||
String username = getCurrentUsername();
|
||||
|
||||
// 填充创建人
|
||||
this.strictInsertFill(metaObject, "createBy", String.class, username);
|
||||
this.strictInsertFill(metaObject, "create_by", String.class, username);
|
||||
|
||||
// 确保tenantId被设置
|
||||
Integer tenantId = getCurrentTenantId();
|
||||
if (tenantId == null) {
|
||||
throw new RuntimeException("无法获取当前租户ID,请确保用户已登录或正确设置租户上下文");
|
||||
}
|
||||
this.strictInsertFill(metaObject, "tenantId", Integer.class, tenantId);
|
||||
this.strictInsertFill(metaObject, "tenant_id", Integer.class, tenantId);
|
||||
}
|
||||
|
||||
// 设置数据修改时的字段自动赋值规则
|
||||
@Override
|
||||
public void updateFill(MetaObject metaObject) {
|
||||
// 填充更新时间
|
||||
Date currentTime = new Date();
|
||||
this.strictUpdateFill(metaObject, "updateTime", Date.class, currentTime);
|
||||
this.strictUpdateFill(metaObject, "update_time", Date.class, currentTime);
|
||||
|
||||
// 填充更新人
|
||||
String username = getCurrentUsername();
|
||||
this.strictUpdateFill(metaObject, "updateBy", String.class, username);
|
||||
this.strictUpdateFill(metaObject, "update_by", String.class, username);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前登录用户名
|
||||
* @return 当前登录用户名,如果无法获取则返回 "system"
|
||||
*/
|
||||
private String getCurrentUsername() {
|
||||
String username = "system"; // 默认值
|
||||
|
||||
try {
|
||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||
if (loginUser != null) {
|
||||
username = loginUser.getUsername();
|
||||
} else {
|
||||
// 尝试从请求中获取用户信息
|
||||
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||
if (attributes != null) {
|
||||
HttpServletRequest request = attributes.getRequest();
|
||||
// 可以在这里添加额外的逻辑来从请求中获取用户信息
|
||||
// 例如从请求头、session等获取用户信息
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 记录异常但不中断处理流程
|
||||
System.err.println("获取当前登录用户时发生异常: " + e.getMessage());
|
||||
// 可以考虑记录日志
|
||||
}
|
||||
|
||||
return username;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前租户 ID
|
||||
*/
|
||||
private Integer getCurrentTenantId() {
|
||||
Integer result = null;
|
||||
|
||||
// 首先尝试从线程局部变量中获取租户ID(适用于定时任务等场景)
|
||||
Integer threadLocalTenantId = TenantContext.getCurrentTenant();
|
||||
if (threadLocalTenantId != null) {
|
||||
result = threadLocalTenantId;
|
||||
} else {
|
||||
// 获取当前登录用户的租户ID(优先使用SecurityUtils中储存的LoginUser的租户ID)
|
||||
try {
|
||||
if (SecurityUtils.getAuthentication() != null) {
|
||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||
if (loginUser != null) {
|
||||
result = loginUser.getTenantId();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 记录异常但不中断处理
|
||||
System.err.println("获取当前登录用户租户ID时发生异常: " + e.getMessage());
|
||||
}
|
||||
|
||||
if (result == null) {
|
||||
// 尝试从请求头中获取租户ID
|
||||
ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
|
||||
if (attributes != null) {
|
||||
HttpServletRequest request = attributes.getRequest();
|
||||
if (request != null) {
|
||||
// 从请求头获取租户ID,假设header名称为"X-Tenant-ID" ; 登录接口前端把租户id放到请求头里
|
||||
String tenantIdHeader = request.getHeader("X-Tenant-ID");
|
||||
String requestMethodName = request.getHeader("Request-Method-Name");
|
||||
// 登录
|
||||
if ("login".equals(requestMethodName)) {
|
||||
if (tenantIdHeader != null && !tenantIdHeader.isEmpty()) {
|
||||
try {
|
||||
result = Integer.parseInt(tenantIdHeader);
|
||||
} catch (NumberFormatException e) {
|
||||
System.err.println("解析请求头中的租户ID时发生异常: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果仍然没有获取到租户ID,返回默认值
|
||||
if (result == null) {
|
||||
System.out.println("警告: 未能获取当前租户ID,将使用默认租户ID 1");
|
||||
result = 1; // 默认租户ID
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package com.openhis;
|
||||
|
||||
/**
|
||||
* 示例类 - 引用 OpenHisApplication
|
||||
*/
|
||||
public class Fragment {
|
||||
|
||||
public static void main(String[] args) {
|
||||
// 引用 OpenHisApplication
|
||||
Class<?> applicationClass = com.openhis.OpenHisApplication.class;
|
||||
System.out.println("Application class: " + applicationClass.getName());
|
||||
}
|
||||
}
|
||||
434
md/需求/104-报卡管理界面_2026-02-05.md
Normal file
434
md/需求/104-报卡管理界面_2026-02-05.md
Normal file
@@ -0,0 +1,434 @@
|
||||
## 报卡管理界面PRD文档
|
||||
|
||||
### 一、页面概述
|
||||
|
||||
**页面名称**:报卡管理界面
|
||||
**页面目标**:提供传染病报卡的审核、管理、筛选及批量操作功能,帮助疾控人员高效完成报卡审核工作
|
||||
**适用场景**:疾控中心工作人员日常审核医疗机构上报的传染病病例
|
||||
|
||||
- 医院CDC管理员日常审核传染病报卡
|
||||
- 批量处理待审核/退回的报卡
|
||||
- 按条件筛选统计报卡数据
|
||||
**页面类型**:数据管理列表页(含详情抽屉)
|
||||
|
||||
**核心功能**:
|
||||
|
||||
1. 报卡数据概览统计(今日待审/本月失败/本月成功/本月上报)
|
||||
2. 多维度筛选报卡数据(时间/状态/科室/来源等)
|
||||
3. 报卡列表展示与批量操作(审核/退回/导出)
|
||||
4. 报卡详情查看与单条审核
|
||||
5. 审核记录追溯与意见填写
|
||||
|
||||
**用户价值**:
|
||||
|
||||
- 疾控人员可快速处理待审报卡
|
||||
- 支持批量审核提升工作效率
|
||||
- 完整记录审核过程便于追溯
|
||||
- 多维度筛选快速定位目标报卡
|
||||
**原型图地址**:https://static.pm-ai.cn/prototype/20260206/cc9991b716df0303fa3459042e33a1ea/index.html
|
||||
**流程图**:
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["进入报卡管理界面"] --> B["查看报卡概览统计"]
|
||||
B --> C["点击统计卡片"]
|
||||
C --> D["悬停统计卡片"]
|
||||
D --> E["自动设置筛选项"]
|
||||
B --> F["鼠标悬停"]
|
||||
F --> G["筛选报卡数据"]
|
||||
G --> H["卡片上浮+阴影"]
|
||||
E --> I["设置筛选条件"]
|
||||
I --> K["点击重置按钮"]
|
||||
K --> J["清空条件"]
|
||||
I --> L["点击查询按钮"]
|
||||
L --> M["触发筛选"]
|
||||
M --> N["操作报卡列表"]
|
||||
N --> O["勾选报卡"]
|
||||
N --> P["处理单条报卡"]
|
||||
O --> Q["批量操作报卡"]
|
||||
P --> R["点击单条审核"]
|
||||
P --> S["点击单条查看"]
|
||||
Q --> T["点击批量审核"]
|
||||
Q --> U["点击批量退回"]
|
||||
R --> V{"报卡状态"}
|
||||
S --> W{"报卡状态"}
|
||||
V -- 已审核 --> X["打开查看抽屉"]
|
||||
V -- 待审核/失败 --> Y["校验选择状态"]
|
||||
W -- 任意 --> X
|
||||
T --> Z["校验选择状态"]
|
||||
U --> AA["校验选择状态"]
|
||||
Y -- 选择有效 --> AB["打开审核抽屉"]
|
||||
AB-->AN["展示审核记录"]
|
||||
AB-->AO["展示审核记录"]
|
||||
Y -- 未选择 --> AC["提示请选择报卡"]
|
||||
Z -- 选择有效 --> AD{"包含已审核项"}
|
||||
Z -- 未选择 --> AE["提示请选择报卡"]
|
||||
AD -- 是 --> AF["提示只能选择待审核报卡"]
|
||||
AD -- 否 --> AG["弹出审核弹窗"]
|
||||
AA -- 选择有效 --> AH{"包含已审核项"}
|
||||
AA -- 未选择 --> AI["提示请选择报卡"]
|
||||
AH -- 是 --> AF
|
||||
AH -- 否 --> AJ["弹出退回弹窗"]
|
||||
AG --> AK["加载报卡详情"]
|
||||
AJ --> AL["加载报卡详情"]
|
||||
AK -- 加载失败 --> AM["提示数据加载失败"]
|
||||
AL -- 加载失败 --> AM
|
||||
AK -- 成功 --> AN["展示审核记录"]
|
||||
AL -- 成功 --> AO["展示审核记录"]
|
||||
AN --> AP["填写审核意见"]
|
||||
AO --> AQ["填写退回原因"]
|
||||
AP --> AR{"意见是否为空"}
|
||||
AQ --> AS{"原因是否为空"}
|
||||
AR -- 是 --> AT["红字提示必填"]
|
||||
AS -- 是 --> AU["红字提示必填"]
|
||||
AR -- 否 --> AV["点击确认审核"]
|
||||
AS -- 否 --> AW["点击确认退回"]
|
||||
AV --> AX["点击审核通过"]
|
||||
AW --> AY["点击退回修改"]
|
||||
AX --> AZ["更新报卡状态"]
|
||||
AY --> BA["更新报卡状态"]
|
||||
AZ --> BB["生成审核记录"]
|
||||
BA --> BC["生成审核记录"]
|
||||
BB --> BD["按钮置灰"]
|
||||
BC --> BD["按钮置灰"]
|
||||
AX --> BE["提示操作失败,请检查网络"]
|
||||
AY --> BE
|
||||
AZ --> BF["刷新表格数据"]
|
||||
BA --> BF
|
||||
BF --> BG["刷新行状态"]
|
||||
BG --> BH["结束"]
|
||||
BE --> BH
|
||||
```
|
||||
|
||||
|
||||
### 二、整体布局分析
|
||||
|
||||
**页面宽度**:自适应布局
|
||||
**主要区域划分**:
|
||||
|
||||
1. **顶部导航栏**(固定高度60px)
|
||||
2. **报卡管理概览区**(统计卡片+快捷操作,高度自适应)
|
||||
3. **筛选控制区**(多条件组合筛选,折叠式布局)
|
||||
4. **报卡列表区**(表格展示,占主体60%高度)
|
||||
**布局特点**:上下层级结构,筛选区支持折叠/展开
|
||||
5. **抽屉详情区**
|
||||
|
||||
|
||||
|
||||
### 三、页面区域详细描述
|
||||
|
||||
#### 1. 顶部导航栏
|
||||
|
||||
**区域位置**:页面顶部固定
|
||||
**区域尺寸**:高度60px,100%宽度
|
||||
**区域功能**:展示系统标识和用户信息
|
||||
**包含元素**:
|
||||
|
||||
- **系统Logo**
|
||||
|
||||
- - 元素类型:图标+文字组合
|
||||
- 显示内容:"CDC"图标+"报卡管理"文字
|
||||
- 样式特征:蓝色主色调(#4a6fa5),左侧对齐
|
||||
|
||||
#### 2. 报卡管理概览
|
||||
|
||||
**区域位置**:导航栏下方
|
||||
|
||||
**区域尺寸**:100%宽度,自适应高度
|
||||
**区域功能**:关键数据统计与快速操作入口
|
||||
**包含元素**:
|
||||
|
||||
- **统计卡片组**(4个)
|
||||
|
||||
- - 展示方式:网格布局(4列)
|
||||
- 数据字段:
|
||||
|
||||
| **字段名** | **类型** | **示例值** | **可操作** | **计算逻辑** |
|
||||
| ------------ | -------- | ---------- | ---------- | ------------------------- |
|
||||
| 今日待审核 | 数字 | 12 | 可点击 | 当天created_at+待审状态 |
|
||||
| 本月审核失败 | 数字 | 3 | 可点击 | 当月created_at+失败状态 |
|
||||
| 本月审核成功 | 数字 | 2 | 可点击 | 当月created_at+成功状态 |
|
||||
| 本月已上报 | 数字 | 156 | 可点击 | 当月created_at+已上报状态 |
|
||||
|
||||
o 交互行为:
|
||||
|
||||
- - - 悬停:卡片上浮5px+阴影加深
|
||||
- 点击:自动设置对应筛选项
|
||||
|
||||
- 样式特征:左侧状态色条(蓝/橙/红/绿)
|
||||
|
||||
#### 3. 筛选控制区
|
||||
|
||||
**区域功能**:多维度组合筛选报卡数据
|
||||
|
||||
**区域尺寸**:100%宽度,高度自适应(展开状态)
|
||||
**包含元素**:
|
||||
|
||||
- **筛选条件组**(横向排列→移动端垂直堆叠)
|
||||
|
||||
- - 登记来源(下拉单选):全部/门诊/住院/急诊/体检
|
||||
- 上报时间范围(双日期选择器)--默认值:最近一个月
|
||||
- 患者姓名(文本输入)
|
||||
- 审核状态(下拉单选):全部/待审核/审核通过/审核失败/已上报
|
||||
- 上报科室(树形下拉多选)--全部科室/取值于《科室管理》adm_organization表
|
||||
|
||||
- **操作按钮**:
|
||||
|
||||
- - “查询”(主按钮,触发筛选)
|
||||
- “重置”(次要按钮,清空条件)
|
||||
|
||||
#### 4. 报卡列表区
|
||||
|
||||
**区域功能**:展示报卡数据及操作入口
|
||||
**包含元素**:
|
||||
|
||||
- **表格头部**
|
||||
|
||||
- - 全选复选框(联动所有行选择状态)
|
||||
- 列标题:报卡名称/病种名称/患者信息等11列
|
||||
|
||||
- **快捷操作按钮组**
|
||||
|
||||
- - 包含按钮:
|
||||
|
||||
- - “批量审核”蓝色按钮,(需选择条目)点击弹出填写审核备注弹窗
|
||||
- “批量退回修改”橙色警示按钮,(需选择条目)点击弹出退回原因填写弹窗
|
||||
- “导出当前”(按筛选条件)
|
||||
|
||||
- **表格行**
|
||||
|
||||
- - 数据字段:
|
||||
|
||||
取值于传染病报卡表(infectious_card)
|
||||
|
||||
| **列名** | **数据类型** | **示例值** | **说明** |
|
||||
| -------- | ------------ | ---------------- | --------------- |
|
||||
| 选择框 | boolean | - | 带全选功能 |
|
||||
| 报卡名称 | string | 传染病报告卡 | - |
|
||||
| 病种名称 | string | 病毒性肝炎 | - |
|
||||
| 报卡编号 | string | HOSP202601150001 | 唯一标识 |
|
||||
| 患者姓名 | string | 张某某 | 脱敏显示 |
|
||||
| 性别 | enum | 男 | 男/女/未知 |
|
||||
| 年龄 | number | 32 | - |
|
||||
| 上报科室 | string | 儿科 | - |
|
||||
| 登记来源 | string | 门诊 | - |
|
||||
| 上报时间 | datetime | 2026-01-31 09:23 | - |
|
||||
| 状态 | badge | 待审核 | 待审核<->已提交 |
|
||||
| 操作 | button | 审核/查看 | 根据状态禁用 |
|
||||
|
||||
|
||||
|
||||
- - 行操作按钮:
|
||||
|
||||
- - “审核”(主按钮,打开可编辑抽屉)
|
||||
- “查看”(次要按钮,打开只读抽屉)
|
||||
|
||||
- 交互规则:
|
||||
|
||||
- - 已审核通过的报卡禁用"审核"按钮
|
||||
- 行hover时显示浅蓝色背景
|
||||
|
||||
- 分页功能:
|
||||
|
||||
- - 实现分页功能(可以设置每页5/10/20条)
|
||||
|
||||
#### 5. 抽屉详情区
|
||||
|
||||
**区域位置**:右侧滑出
|
||||
**区域尺寸**:自适应布局
|
||||
**包含元素**:
|
||||
|
||||
· **报卡表单(****根据具体报卡登记的界面内容,比如《中华人民共和国传染病报告卡》内容和功能与需求编号102界面保持一致,建议用同一个界面)**
|
||||
|
||||
- - 字段分组:
|
||||
|
||||
- 1. 患者基本信息(姓名、身份证等)
|
||||
2. 临床信息(发病日期、诊断分类等)
|
||||
3. 疾病选择(甲/乙/丙类传染病复选框)
|
||||
4. 上报信息(报告单位、医生等)
|
||||
|
||||
· **审核记录**
|
||||
|
||||
- - 展示方式:时间轴列表
|
||||
- 数据字段:时间、操作人、操作类型、意见
|
||||
|
||||
· **操作按钮组**
|
||||
|
||||
- - 主按钮:审核通过(绿色)
|
||||
- 次按钮:退回修改(橙色)
|
||||
|
||||
|
||||
|
||||
### 四、交互功能详细说明
|
||||
|
||||
#### 1. 批量审核流程
|
||||
|
||||
**触发条件**:勾选多行后点击"批量审核"
|
||||
**操作流程**:
|
||||
|
||||
1. 系统校验:至少选择1条非"已通过"状态的报卡
|
||||
|
||||
2. 弹出审核弹窗(500px居中模态框)
|
||||
|
||||
3. 填写审核意见(必填)
|
||||
|
||||
4. 点击"确认审核":
|
||||
|
||||
5. 批量更新状态为"审核通过",(自动批量写入每一条审核记录(插入infectious_audit 字段详表②、更改表infectious_card. Statu= 2和infectious_card. update_time= now()))
|
||||
|
||||
6. 刷新表格数据
|
||||
|
||||
7. - 成功:更新状态为"审核通过",添加审核记录
|
||||
- 失败:提示"审核失败,请检查网络"
|
||||
- 包含已审核项:提示"只能选择待审核报卡"
|
||||
- 未填写意见:阻止提交并红字提示
|
||||
|
||||
#### 2. 批量退回修改操作
|
||||
|
||||
**触发方式**:勾选多选框后点击"批量退回"
|
||||
**前置校验**:
|
||||
|
||||
- 至少勾选一条非"已审核"状态报卡
|
||||
- 选中已审核报卡时提示"只能操作待审核报卡"
|
||||
|
||||
**执行流程**:
|
||||
|
||||
1. 弹出模态框要求填写退回原因(必填)填写退回原因:阻止提交并红字提示
|
||||
2. 提交后批量更新选中报卡状态
|
||||
3. 每条生成审核记录(①、插入infectious_audit 字段详表②、更改表infectious_card. Statu=5和infectious_card. update_time= now())
|
||||
|
||||
#### 3.单卡审核流程
|
||||
|
||||
**触发方式**:点击操作列"审核"按钮
|
||||
**状态控制**:
|
||||
|
||||
- 待审核/审核失败:可操作
|
||||
- 审核通过:按钮禁用
|
||||
**执行流程**:
|
||||
|
||||
1. 右侧滑出审核抽屉
|
||||
2. 从行数据获取报卡ID自动填充患者报卡信息(异步加载报卡详情数据)
|
||||
3. 展示历史审核记录(如有)
|
||||
4. 填写审核意见/退回原因
|
||||
5. 点击"审核通过"或"退回修改"
|
||||
6. 更新表格行状态(生成审核记录(①、插入infectious_audit 字段详表②、更改表infectious_card. Statu=2/5和infectious_card. update_time= now()))
|
||||
|
||||
**异常处理**:
|
||||
|
||||
· 数据加载失败:提示"数据加载失败,请重试"
|
||||
|
||||
**·** 重复提交:按钮置灰防止重复点击
|
||||
|
||||
**状态变化**:
|
||||
|
||||
- 审核通过:表格行变绿色,按钮禁用,状态变成“审核通过”
|
||||
- 退回修改:表格行变橙色,生成退回记录,状态变成“审核失败”审核失败<->退回
|
||||
|
||||
**数据校验**:
|
||||
|
||||
- 必填字段红框提示
|
||||
- 身份证号格式校验
|
||||
- 日期逻辑校验(发病日期≤诊断日期)
|
||||
|
||||
#### 4. 筛选查询功能
|
||||
|
||||
**触发方式**:点击"查询"按钮
|
||||
**查询逻辑**:
|
||||
|
||||
- 登记来源:精确匹配
|
||||
- 患者姓名:模糊匹配
|
||||
- 时间范围:闭区间查询
|
||||
- 状态筛选:精确匹配
|
||||
- 上报科室:多选
|
||||
**性能优化**:
|
||||
- 500ms防抖处理
|
||||
- 分页加载(可以设置每页5/10/20条)实现分页功能
|
||||
|
||||
#### 5. 报卡详情查看
|
||||
|
||||
**触发条件**:点击行"查看"按钮
|
||||
**抽屉内容**:
|
||||
|
||||
- 只读表单(包含所有报卡字段)
|
||||
- 审核记录时间轴(倒序展示)
|
||||
- 关闭按钮(右上角×图标)
|
||||
**数据加载**:根据行数据获取报卡ID自动填充患者报卡信息(异步加载报卡详情数据)
|
||||
|
||||
#### 6. 筛选联动逻辑
|
||||
|
||||
| **操作** | **系统响应** |
|
||||
| ---------------------- | ------------------------------------------ |
|
||||
| 点击"今日待审核"统计卡 | 自动设置: - 时间=当天 - 状态=待审核 |
|
||||
| 本月审核失败 | 自动设置: - 时间=本月 - 状态=审核失败 |
|
||||
| 本月审核成功 | 自动设置: - 时间=本月 - 状态=审核成功 |
|
||||
| 本月已上报 | 自动设置: - 时间=本月 - 状态=已上报 |
|
||||
|
||||
|
||||
|
||||
### 五、数据结构说明
|
||||
|
||||
**关键数据表**:
|
||||
|
||||
```
|
||||
|
||||
infectious_card 与报卡审核记录表(infectious_audit)采用 一对多 关联:
|
||||
① 、一张报卡可经历多次审核(初审、复审、退回、重审等)。
|
||||
② 、关联键:infectious_card.card_no → infectious_audit.card_id(FK)。
|
||||
*infectious_card. Statu增加状态5退回=审核失败(当批量退回修改/退回修改时更改表infectious_card. Statu=5 and infectious_card. update_time= now())
|
||||
|
||||
*infectious_audit 字段详表
|
||||
```
|
||||
|
||||
| **字段名** | **中文名称** | **取值说明** | **类型****(PG)** | **约束** |
|
||||
| ----------------- | ------------ | ------------------------------------------------------------ | ---------------- | ---------------------------------------- |
|
||||
| audit_id | 审核记录ID | 主键,自增 | BIGINT | PRIMARY KEY |
|
||||
| card_id | 报卡ID | 关联infectious_card.card_no | BIGINT | NOT NULL, FK |
|
||||
| audit_seq | 审核序号 | 第几次审核,从1开始 | SMALLINT | NOT NULL, ≥1 |
|
||||
| audit_type | 审核类型 | 1批量审核/2单审核通过/3批量退回修改/4单退回修改 /5其他 | CHAR(1) | NOT NULL, IN('1','2','3','4','5') |
|
||||
| audit_status_from | 审核前状态 | 同infectious_card.status(0暂存/1已提交=待审核/2已审核=审核通过/3已上报/4失败/5退回=审核失败) | CHAR(1) | NOT NULL, IN('0','1','2','3','4','5') |
|
||||
| audit_status_to | 审核后状态 | 同上,审核后的新状态 | CHAR(1) | NOT NULL, IN('0','1','2','3','4') |
|
||||
| audit_time | 审核时间 | 精确到秒,当前时间戳 | TIMESTAMP | NOT NULL, DEFAULT now() |
|
||||
| auditor_id | 审核人账号 | 登录账号 | VARCHAR(20) | NOT NULL |
|
||||
| auditor_name | 审核人姓名 | 登录账号的姓名 | VARCHAR(50) | NOT NULL |
|
||||
| audit_opinion | 审核意见 | 审核意见简述 | TEXT | |
|
||||
| Reason_for_return | 退回原因 | 退回原因简述 | TEXT | |
|
||||
| fail_reason_code | 失败原因码 | 字典:001必填项/002逻辑错误/003网络超时等 | VARCHAR(20) | |
|
||||
| fail_reason_desc | 失败详情 | 详细描述,可空 | TEXT | |
|
||||
| is_batch | 是否批量 | 0单条审核/1批量审核 | BOOLEAN | NOT NULL, DEFAULT false |
|
||||
| batch_size | 批量数量 | 批量时涉及报卡数 | INTEGER | NOT NULL, DEFAULT 1, ≥1 |
|
||||
| created_time | 记录创建时间 | 自动生成 | TIMESTAMP | NOT NULL, DEFAULT now() |
|
||||
| updated_time | 记录更新时间 | 自动更新 | TIMESTAMP | NOT NULL, DEFAULT now(), ON UPDATE now() |
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 六、开发实现要点
|
||||
|
||||
**样式规范**:
|
||||
|
||||
- **主色调**:`#4a6fa5`(导航栏/主按钮)
|
||||
|
||||
- **状态色**:
|
||||
|
||||
- - 待审核:`rgba(74, 111, 165, 0.1)`
|
||||
- 审核失败:`rgba(231, 76, 60, 0.1)`
|
||||
|
||||
- **字体**:
|
||||
|
||||
- - 标题:`16px/1.5 #333`
|
||||
- 表格内容:`14px/1.5 #666`
|
||||
|
||||
**注意事项**:
|
||||
|
||||
- 审核记录需永久保存不可删除
|
||||
|
||||
|
||||
|
||||
### 七、补充说明
|
||||
|
||||
1. **日期格式**:统一使用`YYYY/MM/DD`(符合医疗系统惯例)
|
||||
2. **地址组件**:四级联动(省→市→区→街道)
|
||||
3. **职业选项**:使用国家标准职业分类
|
||||
4. **病种名称**:严格遵循《传染病报告卡》规范用词
|
||||
@@ -1,208 +0,0 @@
|
||||
---
|
||||
name: java-spring-boot
|
||||
description: Build production Spring Boot applications - REST APIs, Security, Data, Actuator
|
||||
sasmp_version: "1.3.0"
|
||||
version: "3.0.0"
|
||||
bonded_agent: 03-java-spring
|
||||
bond_type: PRIMARY_BOND
|
||||
allowed-tools: Read, Write, Bash, Glob, Grep
|
||||
|
||||
# Parameter Validation
|
||||
parameters:
|
||||
spring_version:
|
||||
type: string
|
||||
default: "3.2"
|
||||
description: Spring Boot version
|
||||
module:
|
||||
type: string
|
||||
enum: [web, security, data, actuator, cloud]
|
||||
description: Spring module focus
|
||||
---
|
||||
|
||||
# Java Spring Boot Skill
|
||||
|
||||
Build production-ready Spring Boot applications with modern best practices.
|
||||
|
||||
## Overview
|
||||
|
||||
This skill covers Spring Boot development including REST APIs, security configuration, data access, actuator monitoring, and cloud integration. Follows Spring Boot 3.x patterns with emphasis on production readiness.
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
Use when you need to:
|
||||
- Create REST APIs with Spring MVC/WebFlux
|
||||
- Configure Spring Security (OAuth2, JWT)
|
||||
- Set up database access with Spring Data
|
||||
- Enable monitoring with Actuator
|
||||
- Integrate with Spring Cloud
|
||||
|
||||
## Topics Covered
|
||||
|
||||
### Spring Boot Core
|
||||
- Auto-configuration and starters
|
||||
- Application properties and profiles
|
||||
- Bean lifecycle and configuration
|
||||
- DevTools and hot reload
|
||||
|
||||
### REST API Development
|
||||
- @RestController and @RequestMapping
|
||||
- Request/response handling
|
||||
- Validation with Bean Validation
|
||||
- Exception handling with @ControllerAdvice
|
||||
|
||||
### Spring Security
|
||||
- SecurityFilterChain configuration
|
||||
- OAuth2 and JWT authentication
|
||||
- Method security (@PreAuthorize)
|
||||
- CORS and CSRF configuration
|
||||
|
||||
### Spring Data JPA
|
||||
- Repository pattern
|
||||
- Query methods and @Query
|
||||
- Pagination and sorting
|
||||
- Auditing and transactions
|
||||
|
||||
### Actuator & Monitoring
|
||||
- Health checks and probes
|
||||
- Metrics with Micrometer
|
||||
- Custom endpoints
|
||||
- Prometheus integration
|
||||
|
||||
## Quick Reference
|
||||
|
||||
```java
|
||||
// REST Controller
|
||||
@RestController
|
||||
@RequestMapping("/api/users")
|
||||
@Validated
|
||||
public class UserController {
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ResponseEntity<User> getUser(@PathVariable Long id) {
|
||||
return userService.findById(id)
|
||||
.map(ResponseEntity::ok)
|
||||
.orElse(ResponseEntity.notFound().build());
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<User> createUser(@Valid @RequestBody UserRequest request) {
|
||||
User user = userService.create(request);
|
||||
URI location = URI.create("/api/users/" + user.getId());
|
||||
return ResponseEntity.created(location).body(user);
|
||||
}
|
||||
}
|
||||
|
||||
// Security Configuration
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class SecurityConfig {
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
return http
|
||||
.csrf(csrf -> csrf.disable())
|
||||
.sessionManagement(s -> s.sessionCreationPolicy(STATELESS))
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
.requestMatchers("/actuator/health/**").permitAll()
|
||||
.requestMatchers("/api/public/**").permitAll()
|
||||
.anyRequest().authenticated())
|
||||
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// Exception Handler
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
@ExceptionHandler(EntityNotFoundException.class)
|
||||
public ProblemDetail handleNotFound(EntityNotFoundException ex) {
|
||||
return ProblemDetail.forStatusAndDetail(NOT_FOUND, ex.getMessage());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration Templates
|
||||
|
||||
```yaml
|
||||
# application.yml
|
||||
spring:
|
||||
application:
|
||||
name: ${APP_NAME:my-service}
|
||||
profiles:
|
||||
active: ${SPRING_PROFILES_ACTIVE:local}
|
||||
jpa:
|
||||
open-in-view: false
|
||||
properties:
|
||||
hibernate:
|
||||
jdbc.batch_size: 50
|
||||
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: health,info,metrics,prometheus
|
||||
endpoint:
|
||||
health:
|
||||
probes:
|
||||
enabled: true
|
||||
|
||||
server:
|
||||
error:
|
||||
include-stacktrace: never
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Layer Architecture
|
||||
```
|
||||
Controller → Service → Repository → Database
|
||||
↓ ↓ ↓
|
||||
DTOs Entities Entities
|
||||
```
|
||||
|
||||
### Validation Patterns
|
||||
```java
|
||||
public record CreateUserRequest(
|
||||
@NotBlank @Size(max = 100) String name,
|
||||
@Email @NotBlank String email,
|
||||
@NotNull @Min(18) Integer age
|
||||
) {}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
| Problem | Cause | Solution |
|
||||
|---------|-------|----------|
|
||||
| Bean not found | Missing @Component | Add annotation or @Bean |
|
||||
| Circular dependency | Constructor injection | Use @Lazy or refactor |
|
||||
| 401 Unauthorized | Security config | Check permitAll paths |
|
||||
| Slow startup | Heavy auto-config | Exclude unused starters |
|
||||
|
||||
### Debug Properties
|
||||
```properties
|
||||
debug=true
|
||||
logging.level.org.springframework.security=DEBUG
|
||||
spring.jpa.show-sql=true
|
||||
```
|
||||
|
||||
### Debug Checklist
|
||||
```
|
||||
□ Check /actuator/conditions
|
||||
□ Verify active profiles
|
||||
□ Review security filter chain
|
||||
□ Check bean definitions
|
||||
□ Test health endpoints
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
Skill("java-spring-boot")
|
||||
```
|
||||
|
||||
## Related Skills
|
||||
- `java-testing` - Spring test patterns
|
||||
- `java-jpa-hibernate` - Data access
|
||||
@@ -1,41 +0,0 @@
|
||||
# java-spring-boot Configuration
|
||||
# Category: general
|
||||
# Generated: 2025-12-30
|
||||
|
||||
skill:
|
||||
name: java-spring-boot
|
||||
version: "1.0.0"
|
||||
category: general
|
||||
|
||||
settings:
|
||||
# Default settings for java-spring-boot
|
||||
enabled: true
|
||||
log_level: info
|
||||
|
||||
# Category-specific defaults
|
||||
validation:
|
||||
strict_mode: false
|
||||
auto_fix: false
|
||||
|
||||
output:
|
||||
format: markdown
|
||||
include_examples: true
|
||||
|
||||
# Environment-specific overrides
|
||||
environments:
|
||||
development:
|
||||
log_level: debug
|
||||
validation:
|
||||
strict_mode: false
|
||||
|
||||
production:
|
||||
log_level: warn
|
||||
validation:
|
||||
strict_mode: true
|
||||
|
||||
# Integration settings
|
||||
integrations:
|
||||
# Enable/disable integrations
|
||||
git: true
|
||||
linter: true
|
||||
formatter: true
|
||||
@@ -1,60 +0,0 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "java-spring-boot Configuration Schema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"skill": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"version": {
|
||||
"type": "string",
|
||||
"pattern": "^\\d+\\.\\d+\\.\\d+$"
|
||||
},
|
||||
"category": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"api",
|
||||
"testing",
|
||||
"devops",
|
||||
"security",
|
||||
"database",
|
||||
"frontend",
|
||||
"algorithms",
|
||||
"machine-learning",
|
||||
"cloud",
|
||||
"containers",
|
||||
"general"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"version"
|
||||
]
|
||||
},
|
||||
"settings": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"log_level": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"debug",
|
||||
"info",
|
||||
"warn",
|
||||
"error"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"skill"
|
||||
]
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
# Java Spring Boot Guide
|
||||
|
||||
## Overview
|
||||
|
||||
This guide provides comprehensive documentation for the **java-spring-boot** skill in the custom-plugin-java plugin.
|
||||
|
||||
## Category: General
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Familiarity with general concepts
|
||||
- Development environment set up
|
||||
- Plugin installed and configured
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```bash
|
||||
# Invoke the skill
|
||||
claude "java-spring-boot - [your task description]"
|
||||
|
||||
# Example
|
||||
claude "java-spring-boot - analyze the current implementation"
|
||||
```
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### Key Principles
|
||||
|
||||
1. **Consistency** - Follow established patterns
|
||||
2. **Clarity** - Write readable, maintainable code
|
||||
3. **Quality** - Validate before deployment
|
||||
|
||||
### Best Practices
|
||||
|
||||
- Always validate input data
|
||||
- Handle edge cases explicitly
|
||||
- Document your decisions
|
||||
- Write tests for critical paths
|
||||
|
||||
## Common Tasks
|
||||
|
||||
### Task 1: Basic Implementation
|
||||
|
||||
```python
|
||||
# Example implementation pattern
|
||||
def implement_java_spring_boot(input_data):
|
||||
"""
|
||||
Implement java-spring-boot functionality.
|
||||
|
||||
Args:
|
||||
input_data: Input to process
|
||||
|
||||
Returns:
|
||||
Processed result
|
||||
"""
|
||||
# Validate input
|
||||
if not input_data:
|
||||
raise ValueError("Input required")
|
||||
|
||||
# Process
|
||||
result = process(input_data)
|
||||
|
||||
# Return
|
||||
return result
|
||||
```
|
||||
|
||||
### Task 2: Advanced Usage
|
||||
|
||||
For advanced scenarios, consider:
|
||||
|
||||
- Configuration customization via `assets/config.yaml`
|
||||
- Validation using `scripts/validate.py`
|
||||
- Integration with other skills
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
| Issue | Cause | Solution |
|
||||
|-------|-------|----------|
|
||||
| Skill not found | Not installed | Run plugin sync |
|
||||
| Validation fails | Invalid config | Check config.yaml |
|
||||
| Unexpected output | Missing context | Provide more details |
|
||||
|
||||
## Related Resources
|
||||
|
||||
- SKILL.md - Skill specification
|
||||
- config.yaml - Configuration options
|
||||
- validate.py - Validation script
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 2025-12-30*
|
||||
@@ -1,87 +0,0 @@
|
||||
# Java Spring Boot Patterns
|
||||
|
||||
## Design Patterns
|
||||
|
||||
### Pattern 1: Input Validation
|
||||
|
||||
Always validate input before processing:
|
||||
|
||||
```python
|
||||
def validate_input(data):
|
||||
if data is None:
|
||||
raise ValueError("Data cannot be None")
|
||||
if not isinstance(data, dict):
|
||||
raise TypeError("Data must be a dictionary")
|
||||
return True
|
||||
```
|
||||
|
||||
### Pattern 2: Error Handling
|
||||
|
||||
Use consistent error handling:
|
||||
|
||||
```python
|
||||
try:
|
||||
result = risky_operation()
|
||||
except SpecificError as e:
|
||||
logger.error(f"Operation failed: {e}")
|
||||
handle_error(e)
|
||||
except Exception as e:
|
||||
logger.exception("Unexpected error")
|
||||
raise
|
||||
```
|
||||
|
||||
### Pattern 3: Configuration Loading
|
||||
|
||||
Load and validate configuration:
|
||||
|
||||
```python
|
||||
import yaml
|
||||
|
||||
def load_config(config_path):
|
||||
with open(config_path) as f:
|
||||
config = yaml.safe_load(f)
|
||||
validate_config(config)
|
||||
return config
|
||||
```
|
||||
|
||||
## Anti-Patterns to Avoid
|
||||
|
||||
### ❌ Don't: Swallow Exceptions
|
||||
|
||||
```python
|
||||
# BAD
|
||||
try:
|
||||
do_something()
|
||||
except:
|
||||
pass
|
||||
```
|
||||
|
||||
### ✅ Do: Handle Explicitly
|
||||
|
||||
```python
|
||||
# GOOD
|
||||
try:
|
||||
do_something()
|
||||
except SpecificError as e:
|
||||
logger.warning(f"Expected error: {e}")
|
||||
return default_value
|
||||
```
|
||||
|
||||
## Category-Specific Patterns: General
|
||||
|
||||
### Recommended Approach
|
||||
|
||||
1. Start with the simplest implementation
|
||||
2. Add complexity only when needed
|
||||
3. Test each addition
|
||||
4. Document decisions
|
||||
|
||||
### Common Integration Points
|
||||
|
||||
- Configuration: `assets/config.yaml`
|
||||
- Validation: `scripts/validate.py`
|
||||
- Documentation: `references/GUIDE.md`
|
||||
|
||||
---
|
||||
|
||||
*Pattern library for java-spring-boot skill*
|
||||
@@ -1,131 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Validation script for java-spring-boot skill.
|
||||
Category: general
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import yaml
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def validate_config(config_path: str) -> dict:
|
||||
"""
|
||||
Validate skill configuration file.
|
||||
|
||||
Args:
|
||||
config_path: Path to config.yaml
|
||||
|
||||
Returns:
|
||||
dict: Validation result with 'valid' and 'errors' keys
|
||||
"""
|
||||
errors = []
|
||||
|
||||
if not os.path.exists(config_path):
|
||||
return {"valid": False, "errors": ["Config file not found"]}
|
||||
|
||||
try:
|
||||
with open(config_path, 'r') as f:
|
||||
config = yaml.safe_load(f)
|
||||
except yaml.YAMLError as e:
|
||||
return {"valid": False, "errors": [f"YAML parse error: {e}"]}
|
||||
|
||||
# Validate required fields
|
||||
if 'skill' not in config:
|
||||
errors.append("Missing 'skill' section")
|
||||
else:
|
||||
if 'name' not in config['skill']:
|
||||
errors.append("Missing skill.name")
|
||||
if 'version' not in config['skill']:
|
||||
errors.append("Missing skill.version")
|
||||
|
||||
# Validate settings
|
||||
if 'settings' in config:
|
||||
settings = config['settings']
|
||||
if 'log_level' in settings:
|
||||
valid_levels = ['debug', 'info', 'warn', 'error']
|
||||
if settings['log_level'] not in valid_levels:
|
||||
errors.append(f"Invalid log_level: {settings['log_level']}")
|
||||
|
||||
return {
|
||||
"valid": len(errors) == 0,
|
||||
"errors": errors,
|
||||
"config": config if not errors else None
|
||||
}
|
||||
|
||||
|
||||
def validate_skill_structure(skill_path: str) -> dict:
|
||||
"""
|
||||
Validate skill directory structure.
|
||||
|
||||
Args:
|
||||
skill_path: Path to skill directory
|
||||
|
||||
Returns:
|
||||
dict: Structure validation result
|
||||
"""
|
||||
required_dirs = ['assets', 'scripts', 'references']
|
||||
required_files = ['SKILL.md']
|
||||
|
||||
errors = []
|
||||
|
||||
# Check required files
|
||||
for file in required_files:
|
||||
if not os.path.exists(os.path.join(skill_path, file)):
|
||||
errors.append(f"Missing required file: {file}")
|
||||
|
||||
# Check required directories
|
||||
for dir in required_dirs:
|
||||
dir_path = os.path.join(skill_path, dir)
|
||||
if not os.path.isdir(dir_path):
|
||||
errors.append(f"Missing required directory: {dir}/")
|
||||
else:
|
||||
# Check for real content (not just .gitkeep)
|
||||
files = [f for f in os.listdir(dir_path) if f != '.gitkeep']
|
||||
if not files:
|
||||
errors.append(f"Directory {dir}/ has no real content")
|
||||
|
||||
return {
|
||||
"valid": len(errors) == 0,
|
||||
"errors": errors,
|
||||
"skill_name": os.path.basename(skill_path)
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
"""Main validation entry point."""
|
||||
skill_path = Path(__file__).parent.parent
|
||||
|
||||
print(f"Validating java-spring-boot skill...")
|
||||
print(f"Path: {skill_path}")
|
||||
|
||||
# Validate structure
|
||||
structure_result = validate_skill_structure(str(skill_path))
|
||||
print(f"\nStructure validation: {'PASS' if structure_result['valid'] else 'FAIL'}")
|
||||
if structure_result['errors']:
|
||||
for error in structure_result['errors']:
|
||||
print(f" - {error}")
|
||||
|
||||
# Validate config
|
||||
config_path = skill_path / 'assets' / 'config.yaml'
|
||||
if config_path.exists():
|
||||
config_result = validate_config(str(config_path))
|
||||
print(f"\nConfig validation: {'PASS' if config_result['valid'] else 'FAIL'}")
|
||||
if config_result['errors']:
|
||||
for error in config_result['errors']:
|
||||
print(f" - {error}")
|
||||
else:
|
||||
print("\nConfig validation: SKIPPED (no config.yaml)")
|
||||
|
||||
# Summary
|
||||
all_valid = structure_result['valid']
|
||||
print(f"\n==================================================")
|
||||
print(f"Overall: {'VALID' if all_valid else 'INVALID'}")
|
||||
|
||||
return 0 if all_valid else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -1,94 +0,0 @@
|
||||
---
|
||||
name: spring-boot-engineer
|
||||
description: Use when building Spring Boot 3.x applications, microservices, or reactive Java applications. Invoke for Spring Data JPA, Spring Security 6, WebFlux, Spring Cloud integration.
|
||||
license: MIT
|
||||
metadata:
|
||||
author: https://github.com/Jeffallan
|
||||
version: "1.0.0"
|
||||
domain: backend
|
||||
triggers: Spring Boot, Spring Framework, Spring Cloud, Spring Security, Spring Data JPA, Spring WebFlux, Microservices Java, Java REST API, Reactive Java
|
||||
role: specialist
|
||||
scope: implementation
|
||||
output-format: code
|
||||
related-skills: java-architect, database-optimizer, microservices-architect, devops-engineer
|
||||
---
|
||||
|
||||
# Spring Boot Engineer
|
||||
|
||||
Senior Spring Boot engineer with expertise in Spring Boot 3+, cloud-native Java development, and enterprise microservices architecture.
|
||||
|
||||
## Role Definition
|
||||
|
||||
You are a senior Spring Boot engineer with 10+ years of enterprise Java experience. You specialize in Spring Boot 3.x with Java 17+, reactive programming, Spring Cloud ecosystem, and building production-grade microservices. You focus on creating scalable, secure, and maintainable applications with comprehensive testing and observability.
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
- Building REST APIs with Spring Boot
|
||||
- Implementing reactive applications with WebFlux
|
||||
- Setting up Spring Data JPA repositories
|
||||
- Implementing Spring Security 6 authentication
|
||||
- Creating microservices with Spring Cloud
|
||||
- Optimizing Spring Boot performance
|
||||
- Writing comprehensive tests with Spring Boot Test
|
||||
|
||||
## Core Workflow
|
||||
|
||||
1. **Analyze requirements** - Identify service boundaries, APIs, data models, security needs
|
||||
2. **Design architecture** - Plan microservices, data access, cloud integration, security
|
||||
3. **Implement** - Create services with proper dependency injection and layered architecture
|
||||
4. **Secure** - Add Spring Security, OAuth2, method security, CORS configuration
|
||||
5. **Test** - Write unit, integration, and slice tests with high coverage
|
||||
6. **Deploy** - Configure for cloud deployment with health checks and observability
|
||||
|
||||
## Reference Guide
|
||||
|
||||
Load detailed guidance based on context:
|
||||
|
||||
| Topic | Reference | Load When |
|
||||
|-------|-----------|-----------|
|
||||
| Web Layer | `references/web.md` | Controllers, REST APIs, validation, exception handling |
|
||||
| Data Access | `references/data.md` | Spring Data JPA, repositories, transactions, projections |
|
||||
| Security | `references/security.md` | Spring Security 6, OAuth2, JWT, method security |
|
||||
| Cloud Native | `references/cloud.md` | Spring Cloud, Config, Discovery, Gateway, resilience |
|
||||
| Testing | `references/testing.md` | @SpringBootTest, MockMvc, Testcontainers, test slices |
|
||||
|
||||
## Constraints
|
||||
|
||||
### MUST DO
|
||||
- Use Spring Boot 3.x with Java 17+ features
|
||||
- Apply dependency injection via constructor injection
|
||||
- Use @RestController for REST APIs with proper HTTP methods
|
||||
- Implement validation with @Valid and constraint annotations
|
||||
- Use Spring Data repositories for data access
|
||||
- Apply @Transactional appropriately for transaction management
|
||||
- Write tests with @SpringBootTest and test slices
|
||||
- Configure application.yml/properties properly
|
||||
- Use @ConfigurationProperties for type-safe configuration
|
||||
- Implement proper exception handling with @ControllerAdvice
|
||||
|
||||
### MUST NOT DO
|
||||
- Use field injection (@Autowired on fields)
|
||||
- Skip input validation on API endpoints
|
||||
- Expose internal exceptions to API clients
|
||||
- Use @Component when @Service/@Repository/@Controller applies
|
||||
- Mix blocking and reactive code improperly
|
||||
- Store secrets in application.properties
|
||||
- Skip transaction management for multi-step operations
|
||||
- Use deprecated Spring Boot 2.x patterns
|
||||
- Hardcode URLs, credentials, or configuration
|
||||
|
||||
## Output Templates
|
||||
|
||||
When implementing Spring Boot features, provide:
|
||||
1. Entity/model classes with JPA annotations
|
||||
2. Repository interfaces extending Spring Data
|
||||
3. Service layer with business logic
|
||||
4. Controller with REST endpoints
|
||||
5. DTO classes for API requests/responses
|
||||
6. Configuration classes if needed
|
||||
7. Test classes with appropriate test slices
|
||||
8. Brief explanation of architecture decisions
|
||||
|
||||
## Knowledge Reference
|
||||
|
||||
Spring Boot 3.x, Spring Framework 6, Spring Data JPA, Spring Security 6, Spring Cloud, Project Reactor (WebFlux), JPA/Hibernate, Bean Validation, RestTemplate/WebClient, Actuator, Micrometer, JUnit 5, Mockito, Testcontainers, Docker, Kubernetes
|
||||
@@ -1,498 +0,0 @@
|
||||
# Cloud Native - Spring Cloud
|
||||
|
||||
## Spring Cloud Config Server
|
||||
|
||||
```java
|
||||
// Config Server
|
||||
@SpringBootApplication
|
||||
@EnableConfigServer
|
||||
public class ConfigServerApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(ConfigServerApplication.class, args);
|
||||
}
|
||||
}
|
||||
|
||||
// application.yml
|
||||
server:
|
||||
port: 8888
|
||||
|
||||
spring:
|
||||
cloud:
|
||||
config:
|
||||
server:
|
||||
git:
|
||||
uri: https://github.com/example/config-repo
|
||||
default-label: main
|
||||
search-paths: '{application}'
|
||||
username: ${GIT_USERNAME}
|
||||
password: ${GIT_PASSWORD}
|
||||
native:
|
||||
search-locations: classpath:/config
|
||||
security:
|
||||
user:
|
||||
name: config-user
|
||||
password: ${CONFIG_PASSWORD}
|
||||
|
||||
// Config Client
|
||||
@SpringBootApplication
|
||||
public class ClientApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(ClientApplication.class, args);
|
||||
}
|
||||
}
|
||||
|
||||
// application.yml (Config Client)
|
||||
spring:
|
||||
application:
|
||||
name: user-service
|
||||
config:
|
||||
import: "configserver:http://localhost:8888"
|
||||
cloud:
|
||||
config:
|
||||
username: config-user
|
||||
password: ${CONFIG_PASSWORD}
|
||||
fail-fast: true
|
||||
retry:
|
||||
max-attempts: 6
|
||||
initial-interval: 1000
|
||||
```
|
||||
|
||||
## Dynamic Configuration Refresh
|
||||
|
||||
```java
|
||||
@RestController
|
||||
@RefreshScope
|
||||
public class ConfigController {
|
||||
@Value("${app.feature.enabled:false}")
|
||||
private boolean featureEnabled;
|
||||
|
||||
@Value("${app.max-connections:100}")
|
||||
private int maxConnections;
|
||||
|
||||
@GetMapping("/config")
|
||||
public Map<String, Object> getConfig() {
|
||||
return Map.of(
|
||||
"featureEnabled", featureEnabled,
|
||||
"maxConnections", maxConnections
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh configuration via Actuator endpoint:
|
||||
// POST /actuator/refresh
|
||||
```
|
||||
|
||||
## Service Discovery - Eureka
|
||||
|
||||
```java
|
||||
// Eureka Server
|
||||
@SpringBootApplication
|
||||
@EnableEurekaServer
|
||||
public class EurekaServerApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(EurekaServerApplication.class, args);
|
||||
}
|
||||
}
|
||||
|
||||
// application.yml (Eureka Server)
|
||||
server:
|
||||
port: 8761
|
||||
|
||||
eureka:
|
||||
instance:
|
||||
hostname: localhost
|
||||
client:
|
||||
register-with-eureka: false
|
||||
fetch-registry: false
|
||||
service-url:
|
||||
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
|
||||
|
||||
// Eureka Client
|
||||
@SpringBootApplication
|
||||
@EnableDiscoveryClient
|
||||
public class UserServiceApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(UserServiceApplication.class, args);
|
||||
}
|
||||
}
|
||||
|
||||
// application.yml (Eureka Client)
|
||||
spring:
|
||||
application:
|
||||
name: user-service
|
||||
|
||||
eureka:
|
||||
client:
|
||||
service-url:
|
||||
defaultZone: http://localhost:8761/eureka/
|
||||
registry-fetch-interval-seconds: 5
|
||||
instance:
|
||||
prefer-ip-address: true
|
||||
lease-renewal-interval-in-seconds: 10
|
||||
lease-expiration-duration-in-seconds: 30
|
||||
```
|
||||
|
||||
## Spring Cloud Gateway
|
||||
|
||||
```java
|
||||
@SpringBootApplication
|
||||
public class GatewayApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(GatewayApplication.class, args);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
|
||||
return builder.routes()
|
||||
.route("user-service", r -> r
|
||||
.path("/api/users/**")
|
||||
.filters(f -> f
|
||||
.rewritePath("/api/users/(?<segment>.*)", "/users/${segment}")
|
||||
.addRequestHeader("X-Gateway", "Spring-Cloud-Gateway")
|
||||
.circuitBreaker(config -> config
|
||||
.setName("userServiceCircuitBreaker")
|
||||
.setFallbackUri("forward:/fallback/users")
|
||||
)
|
||||
.retry(config -> config
|
||||
.setRetries(3)
|
||||
.setStatuses(HttpStatus.SERVICE_UNAVAILABLE)
|
||||
)
|
||||
)
|
||||
.uri("lb://user-service")
|
||||
)
|
||||
.route("order-service", r -> r
|
||||
.path("/api/orders/**")
|
||||
.filters(f -> f
|
||||
.rewritePath("/api/orders/(?<segment>.*)", "/orders/${segment}")
|
||||
.requestRateLimiter(config -> config
|
||||
.setRateLimiter(redisRateLimiter())
|
||||
.setKeyResolver(userKeyResolver())
|
||||
)
|
||||
)
|
||||
.uri("lb://order-service")
|
||||
)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RedisRateLimiter redisRateLimiter() {
|
||||
return new RedisRateLimiter(10, 20); // replenishRate, burstCapacity
|
||||
}
|
||||
|
||||
@Bean
|
||||
public KeyResolver userKeyResolver() {
|
||||
return exchange -> Mono.just(
|
||||
exchange.getRequest().getHeaders().getFirst("X-User-Id")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// application.yml (Gateway)
|
||||
spring:
|
||||
cloud:
|
||||
gateway:
|
||||
discovery:
|
||||
locator:
|
||||
enabled: true
|
||||
lower-case-service-id: true
|
||||
default-filters:
|
||||
- DedupeResponseHeader=Access-Control-Allow-Origin
|
||||
globalcors:
|
||||
cors-configurations:
|
||||
'[/**]':
|
||||
allowed-origins: "*"
|
||||
allowed-methods:
|
||||
- GET
|
||||
- POST
|
||||
- PUT
|
||||
- DELETE
|
||||
allowed-headers: "*"
|
||||
```
|
||||
|
||||
## Circuit Breaker - Resilience4j
|
||||
|
||||
```java
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ExternalApiService {
|
||||
private final WebClient webClient;
|
||||
|
||||
@CircuitBreaker(name = "externalApi", fallbackMethod = "getFallbackData")
|
||||
@Retry(name = "externalApi")
|
||||
@RateLimiter(name = "externalApi")
|
||||
public Mono<ExternalData> getData(String id) {
|
||||
return webClient
|
||||
.get()
|
||||
.uri("/data/{id}", id)
|
||||
.retrieve()
|
||||
.bodyToMono(ExternalData.class)
|
||||
.timeout(Duration.ofSeconds(3));
|
||||
}
|
||||
|
||||
private Mono<ExternalData> getFallbackData(String id, Exception e) {
|
||||
log.warn("Fallback triggered for id: {}, error: {}", id, e.getMessage());
|
||||
return Mono.just(new ExternalData(id, "Fallback data", LocalDateTime.now()));
|
||||
}
|
||||
}
|
||||
|
||||
// application.yml
|
||||
resilience4j:
|
||||
circuitbreaker:
|
||||
instances:
|
||||
externalApi:
|
||||
register-health-indicator: true
|
||||
sliding-window-size: 10
|
||||
minimum-number-of-calls: 5
|
||||
permitted-number-of-calls-in-half-open-state: 3
|
||||
automatic-transition-from-open-to-half-open-enabled: true
|
||||
wait-duration-in-open-state: 5s
|
||||
failure-rate-threshold: 50
|
||||
event-consumer-buffer-size: 10
|
||||
|
||||
retry:
|
||||
instances:
|
||||
externalApi:
|
||||
max-attempts: 3
|
||||
wait-duration: 1s
|
||||
enable-exponential-backoff: true
|
||||
exponential-backoff-multiplier: 2
|
||||
|
||||
ratelimiter:
|
||||
instances:
|
||||
externalApi:
|
||||
limit-for-period: 10
|
||||
limit-refresh-period: 1s
|
||||
timeout-duration: 0s
|
||||
```
|
||||
|
||||
## Distributed Tracing - Micrometer Tracing
|
||||
|
||||
```java
|
||||
// application.yml
|
||||
management:
|
||||
tracing:
|
||||
sampling:
|
||||
probability: 1.0
|
||||
zipkin:
|
||||
tracing:
|
||||
endpoint: http://localhost:9411/api/v2/spans
|
||||
|
||||
logging:
|
||||
pattern:
|
||||
level: "%5p [${spring.application.name:},%X{traceId:-},%X{spanId:-}]"
|
||||
|
||||
// Custom spans
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class OrderService {
|
||||
private final Tracer tracer;
|
||||
private final OrderRepository orderRepository;
|
||||
|
||||
public Order processOrder(OrderRequest request) {
|
||||
Span span = tracer.nextSpan().name("processOrder").start();
|
||||
try (Tracer.SpanInScope ws = tracer.withSpan(span)) {
|
||||
span.tag("order.type", request.type());
|
||||
span.tag("order.items", String.valueOf(request.items().size()));
|
||||
|
||||
// Business logic
|
||||
Order order = createOrder(request);
|
||||
|
||||
span.event("order.created");
|
||||
return order;
|
||||
} finally {
|
||||
span.end();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Load Balancing with Spring Cloud LoadBalancer
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
@LoadBalancerClient(name = "user-service", configuration = UserServiceLoadBalancerConfig.class)
|
||||
public class LoadBalancerConfiguration {
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public class UserServiceLoadBalancerConfig {
|
||||
|
||||
@Bean
|
||||
public ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(
|
||||
LoadBalancerClientFactory clientFactory,
|
||||
ObjectProvider<LoadBalancerProperties> properties) {
|
||||
return new RandomLoadBalancer(
|
||||
clientFactory.getLazyProvider("user-service", ServiceInstanceListSupplier.class),
|
||||
"user-service"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class UserClientService {
|
||||
private final WebClient.Builder webClientBuilder;
|
||||
|
||||
public Mono<User> getUser(Long id) {
|
||||
return webClientBuilder
|
||||
.baseUrl("http://user-service")
|
||||
.build()
|
||||
.get()
|
||||
.uri("/users/{id}", id)
|
||||
.retrieve()
|
||||
.bodyToMono(User.class);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Health Checks & Actuator
|
||||
|
||||
```java
|
||||
@Component
|
||||
public class CustomHealthIndicator implements HealthIndicator {
|
||||
|
||||
@Override
|
||||
public Health health() {
|
||||
boolean serviceUp = checkExternalService();
|
||||
|
||||
if (serviceUp) {
|
||||
return Health.up()
|
||||
.withDetail("externalService", "Available")
|
||||
.withDetail("timestamp", LocalDateTime.now())
|
||||
.build();
|
||||
} else {
|
||||
return Health.down()
|
||||
.withDetail("externalService", "Unavailable")
|
||||
.withDetail("error", "Connection timeout")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkExternalService() {
|
||||
// Check external dependency
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// application.yml
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: health,info,metrics,prometheus
|
||||
endpoint:
|
||||
health:
|
||||
show-details: always
|
||||
probes:
|
||||
enabled: true
|
||||
health:
|
||||
livenessState:
|
||||
enabled: true
|
||||
readinessState:
|
||||
enabled: true
|
||||
metrics:
|
||||
export:
|
||||
prometheus:
|
||||
enabled: true
|
||||
tags:
|
||||
application: ${spring.application.name}
|
||||
```
|
||||
|
||||
## Kubernetes Deployment
|
||||
|
||||
```yaml
|
||||
# deployment.yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: user-service
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: user-service
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: user-service
|
||||
spec:
|
||||
containers:
|
||||
- name: user-service
|
||||
image: user-service:1.0.0
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
env:
|
||||
- name: SPRING_PROFILES_ACTIVE
|
||||
value: "kubernetes"
|
||||
- name: JAVA_OPTS
|
||||
value: "-Xmx512m -Xms256m"
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /actuator/health/liveness
|
||||
port: 8080
|
||||
initialDelaySeconds: 60
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /actuator/health/readiness
|
||||
port: 8080
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 5
|
||||
resources:
|
||||
requests:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
limits:
|
||||
memory: "1Gi"
|
||||
cpu: "1000m"
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: user-service
|
||||
spec:
|
||||
selector:
|
||||
app: user-service
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 8080
|
||||
type: ClusterIP
|
||||
```
|
||||
|
||||
## Docker Configuration
|
||||
|
||||
```dockerfile
|
||||
# Dockerfile (Multi-stage)
|
||||
FROM eclipse-temurin:17-jdk-alpine AS build
|
||||
WORKDIR /workspace/app
|
||||
|
||||
COPY mvnw .
|
||||
COPY .mvn .mvn
|
||||
COPY pom.xml .
|
||||
COPY src src
|
||||
|
||||
RUN ./mvnw install -DskipTests
|
||||
RUN mkdir -p target/dependency && (cd target/dependency; jar -xf ../*.jar)
|
||||
|
||||
FROM eclipse-temurin:17-jre-alpine
|
||||
VOLUME /tmp
|
||||
ARG DEPENDENCY=/workspace/app/target/dependency
|
||||
COPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/lib
|
||||
COPY --from=build ${DEPENDENCY}/META-INF /app/META-INF
|
||||
COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app
|
||||
|
||||
ENTRYPOINT ["java","-cp","app:app/lib/*","com.example.Application"]
|
||||
```
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Component | Purpose |
|
||||
|-----------|---------|
|
||||
| **Config Server** | Centralized configuration management |
|
||||
| **Eureka** | Service discovery and registration |
|
||||
| **Gateway** | API gateway with routing, filtering, load balancing |
|
||||
| **Circuit Breaker** | Fault tolerance and fallback patterns |
|
||||
| **Load Balancer** | Client-side load balancing |
|
||||
| **Tracing** | Distributed tracing across services |
|
||||
| **Actuator** | Production-ready monitoring and management |
|
||||
| **Kubernetes** | Container orchestration and deployment |
|
||||
@@ -1,381 +0,0 @@
|
||||
# Data Access - Spring Data JPA
|
||||
|
||||
## JPA Entity Pattern
|
||||
|
||||
```java
|
||||
@Entity
|
||||
@Table(name = "users", indexes = {
|
||||
@Index(name = "idx_email", columnList = "email", unique = true),
|
||||
@Index(name = "idx_username", columnList = "username")
|
||||
})
|
||||
@EntityListeners(AuditingEntityListener.class)
|
||||
@Getter @Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class User {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(nullable = false, unique = true, length = 100)
|
||||
private String email;
|
||||
|
||||
@Column(nullable = false, length = 100)
|
||||
private String password;
|
||||
|
||||
@Column(nullable = false, unique = true, length = 50)
|
||||
private String username;
|
||||
|
||||
@Column(nullable = false)
|
||||
@Builder.Default
|
||||
private Boolean active = true;
|
||||
|
||||
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
|
||||
@Builder.Default
|
||||
private List<Address> addresses = new ArrayList<>();
|
||||
|
||||
@ManyToMany
|
||||
@JoinTable(
|
||||
name = "user_roles",
|
||||
joinColumns = @JoinColumn(name = "user_id"),
|
||||
inverseJoinColumns = @JoinColumn(name = "role_id")
|
||||
)
|
||||
@Builder.Default
|
||||
private Set<Role> roles = new HashSet<>();
|
||||
|
||||
@CreatedDate
|
||||
@Column(nullable = false, updatable = false)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@LastModifiedDate
|
||||
@Column(nullable = false)
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
@Version
|
||||
private Long version;
|
||||
|
||||
// Helper methods for bidirectional relationships
|
||||
public void addAddress(Address address) {
|
||||
addresses.add(address);
|
||||
address.setUser(this);
|
||||
}
|
||||
|
||||
public void removeAddress(Address address) {
|
||||
addresses.remove(address);
|
||||
address.setUser(null);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Spring Data JPA Repository
|
||||
|
||||
```java
|
||||
@Repository
|
||||
public interface UserRepository extends JpaRepository<User, Long>,
|
||||
JpaSpecificationExecutor<User> {
|
||||
|
||||
Optional<User> findByEmail(String email);
|
||||
|
||||
Optional<User> findByUsername(String username);
|
||||
|
||||
boolean existsByEmail(String email);
|
||||
|
||||
boolean existsByUsername(String username);
|
||||
|
||||
@Query("SELECT u FROM User u LEFT JOIN FETCH u.roles WHERE u.email = :email")
|
||||
Optional<User> findByEmailWithRoles(@Param("email") String email);
|
||||
|
||||
@Query("SELECT u FROM User u WHERE u.active = true AND u.createdAt >= :since")
|
||||
List<User> findActiveUsersSince(@Param("since") LocalDateTime since);
|
||||
|
||||
@Modifying
|
||||
@Query("UPDATE User u SET u.active = false WHERE u.lastLoginAt < :threshold")
|
||||
int deactivateInactiveUsers(@Param("threshold") LocalDateTime threshold);
|
||||
|
||||
// Projection for read-only DTOs
|
||||
@Query("SELECT new com.example.dto.UserSummary(u.id, u.username, u.email) " +
|
||||
"FROM User u WHERE u.active = true")
|
||||
List<UserSummary> findAllActiveSummaries();
|
||||
}
|
||||
```
|
||||
|
||||
## Repository with Specifications
|
||||
|
||||
```java
|
||||
public class UserSpecifications {
|
||||
|
||||
public static Specification<User> hasEmail(String email) {
|
||||
return (root, query, cb) ->
|
||||
email == null ? null : cb.equal(root.get("email"), email);
|
||||
}
|
||||
|
||||
public static Specification<User> isActive() {
|
||||
return (root, query, cb) -> cb.isTrue(root.get("active"));
|
||||
}
|
||||
|
||||
public static Specification<User> createdAfter(LocalDateTime date) {
|
||||
return (root, query, cb) ->
|
||||
date == null ? null : cb.greaterThanOrEqualTo(root.get("createdAt"), date);
|
||||
}
|
||||
|
||||
public static Specification<User> hasRole(String roleName) {
|
||||
return (root, query, cb) -> {
|
||||
Join<User, Role> roles = root.join("roles", JoinType.INNER);
|
||||
return cb.equal(roles.get("name"), roleName);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Usage in service
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class UserService {
|
||||
private final UserRepository userRepository;
|
||||
|
||||
public Page<User> searchUsers(UserSearchCriteria criteria, Pageable pageable) {
|
||||
Specification<User> spec = Specification
|
||||
.where(UserSpecifications.hasEmail(criteria.email()))
|
||||
.and(UserSpecifications.isActive())
|
||||
.and(UserSpecifications.createdAfter(criteria.createdAfter()));
|
||||
|
||||
return userRepository.findAll(spec, pageable);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Transaction Management
|
||||
|
||||
```java
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional(readOnly = true)
|
||||
public class OrderService {
|
||||
private final OrderRepository orderRepository;
|
||||
private final PaymentService paymentService;
|
||||
private final InventoryService inventoryService;
|
||||
private final NotificationService notificationService;
|
||||
|
||||
@Transactional
|
||||
public Order createOrder(OrderCreateRequest request) {
|
||||
// All operations in single transaction
|
||||
Order order = Order.builder()
|
||||
.customerId(request.customerId())
|
||||
.status(OrderStatus.PENDING)
|
||||
.build();
|
||||
|
||||
request.items().forEach(item -> {
|
||||
inventoryService.reserveStock(item.productId(), item.quantity());
|
||||
order.addItem(item);
|
||||
});
|
||||
|
||||
order = orderRepository.save(order);
|
||||
|
||||
try {
|
||||
paymentService.processPayment(order);
|
||||
order.setStatus(OrderStatus.PAID);
|
||||
} catch (PaymentException e) {
|
||||
order.setStatus(OrderStatus.PAYMENT_FAILED);
|
||||
throw e; // Transaction will rollback
|
||||
}
|
||||
|
||||
return orderRepository.save(order);
|
||||
}
|
||||
|
||||
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
||||
public void logOrderEvent(Long orderId, String event) {
|
||||
// Separate transaction - will commit even if parent rolls back
|
||||
OrderEvent orderEvent = new OrderEvent(orderId, event);
|
||||
orderEventRepository.save(orderEvent);
|
||||
}
|
||||
|
||||
@Transactional(noRollbackFor = NotificationException.class)
|
||||
public void completeOrder(Long orderId) {
|
||||
Order order = orderRepository.findById(orderId)
|
||||
.orElseThrow(() -> new ResourceNotFoundException("Order not found"));
|
||||
|
||||
order.setStatus(OrderStatus.COMPLETED);
|
||||
orderRepository.save(order);
|
||||
|
||||
// Won't rollback transaction if notification fails
|
||||
try {
|
||||
notificationService.sendCompletionEmail(order);
|
||||
} catch (NotificationException e) {
|
||||
log.error("Failed to send notification for order {}", orderId, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Auditing Configuration
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
@EnableJpaAuditing
|
||||
public class JpaAuditingConfig {
|
||||
|
||||
@Bean
|
||||
public AuditorAware<String> auditorProvider() {
|
||||
return () -> {
|
||||
Authentication authentication = SecurityContextHolder
|
||||
.getContext()
|
||||
.getAuthentication();
|
||||
|
||||
if (authentication == null || !authentication.isAuthenticated()) {
|
||||
return Optional.of("system");
|
||||
}
|
||||
|
||||
return Optional.of(authentication.getName());
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@MappedSuperclass
|
||||
@EntityListeners(AuditingEntityListener.class)
|
||||
@Getter @Setter
|
||||
public abstract class AuditableEntity {
|
||||
|
||||
@CreatedDate
|
||||
@Column(nullable = false, updatable = false)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@CreatedBy
|
||||
@Column(nullable = false, updatable = false, length = 100)
|
||||
private String createdBy;
|
||||
|
||||
@LastModifiedDate
|
||||
@Column(nullable = false)
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
@LastModifiedBy
|
||||
@Column(nullable = false, length = 100)
|
||||
private String updatedBy;
|
||||
}
|
||||
```
|
||||
|
||||
## Projections
|
||||
|
||||
```java
|
||||
// Interface-based projection
|
||||
public interface UserSummary {
|
||||
Long getId();
|
||||
String getUsername();
|
||||
String getEmail();
|
||||
|
||||
@Value("#{target.firstName + ' ' + target.lastName}")
|
||||
String getFullName();
|
||||
}
|
||||
|
||||
// Class-based projection (DTO)
|
||||
public record UserSummaryDto(
|
||||
Long id,
|
||||
String username,
|
||||
String email
|
||||
) {}
|
||||
|
||||
// Usage
|
||||
public interface UserRepository extends JpaRepository<User, Long> {
|
||||
List<UserSummary> findAllBy();
|
||||
|
||||
<T> List<T> findAllBy(Class<T> type);
|
||||
}
|
||||
|
||||
// Service usage
|
||||
List<UserSummary> summaries = userRepository.findAllBy();
|
||||
List<UserSummaryDto> dtos = userRepository.findAllBy(UserSummaryDto.class);
|
||||
```
|
||||
|
||||
## Query Optimization
|
||||
|
||||
```java
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional(readOnly = true)
|
||||
public class UserQueryService {
|
||||
private final UserRepository userRepository;
|
||||
private final EntityManager entityManager;
|
||||
|
||||
// N+1 problem solved with JOIN FETCH
|
||||
@Query("SELECT DISTINCT u FROM User u " +
|
||||
"LEFT JOIN FETCH u.addresses " +
|
||||
"LEFT JOIN FETCH u.roles " +
|
||||
"WHERE u.active = true")
|
||||
List<User> findAllActiveWithAssociations();
|
||||
|
||||
// Batch fetching
|
||||
@BatchSize(size = 25)
|
||||
@OneToMany(mappedBy = "user")
|
||||
private List<Order> orders;
|
||||
|
||||
// EntityGraph for dynamic fetching
|
||||
@EntityGraph(attributePaths = {"addresses", "roles"})
|
||||
List<User> findAllByActiveTrue();
|
||||
|
||||
// Pagination to avoid loading all data
|
||||
public Page<User> findAllUsers(Pageable pageable) {
|
||||
return userRepository.findAll(pageable);
|
||||
}
|
||||
|
||||
// Native query for complex queries
|
||||
@Query(value = """
|
||||
SELECT u.* FROM users u
|
||||
INNER JOIN orders o ON u.id = o.user_id
|
||||
WHERE o.created_at >= :since
|
||||
GROUP BY u.id
|
||||
HAVING COUNT(o.id) >= :minOrders
|
||||
""", nativeQuery = true)
|
||||
List<User> findFrequentBuyers(@Param("since") LocalDateTime since,
|
||||
@Param("minOrders") int minOrders);
|
||||
}
|
||||
```
|
||||
|
||||
## Database Migrations (Flyway)
|
||||
|
||||
```sql
|
||||
-- V1__create_users_table.sql
|
||||
CREATE TABLE users (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
email VARCHAR(100) NOT NULL UNIQUE,
|
||||
password VARCHAR(100) NOT NULL,
|
||||
username VARCHAR(50) NOT NULL UNIQUE,
|
||||
active BOOLEAN NOT NULL DEFAULT true,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
version BIGINT NOT NULL DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE INDEX idx_users_email ON users(email);
|
||||
CREATE INDEX idx_users_username ON users(username);
|
||||
CREATE INDEX idx_users_active ON users(active);
|
||||
|
||||
-- V2__create_addresses_table.sql
|
||||
CREATE TABLE addresses (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
street VARCHAR(200) NOT NULL,
|
||||
city VARCHAR(100) NOT NULL,
|
||||
country VARCHAR(2) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX idx_addresses_user_id ON addresses(user_id);
|
||||
```
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Annotation | Purpose |
|
||||
|------------|---------|
|
||||
| `@Entity` | Marks class as JPA entity |
|
||||
| `@Table` | Specifies table details and indexes |
|
||||
| `@Id` | Marks primary key field |
|
||||
| `@GeneratedValue` | Auto-generated primary key strategy |
|
||||
| `@Column` | Column constraints and mapping |
|
||||
| `@OneToMany/@ManyToOne` | One-to-many/many-to-one relationships |
|
||||
| `@ManyToMany` | Many-to-many relationships |
|
||||
| `@JoinColumn/@JoinTable` | Join column/table configuration |
|
||||
| `@Transactional` | Declares transaction boundaries |
|
||||
| `@Query` | Custom JPQL/native queries |
|
||||
| `@Modifying` | Marks query as UPDATE/DELETE |
|
||||
| `@EntityGraph` | Defines fetch graph for associations |
|
||||
| `@Version` | Optimistic locking version field |
|
||||
@@ -1,459 +0,0 @@
|
||||
# Security - Spring Security 6
|
||||
|
||||
## Security Configuration
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableMethodSecurity
|
||||
public class SecurityConfig {
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.csrf(csrf -> csrf
|
||||
.ignoringRequestMatchers("/api/auth/**")
|
||||
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
|
||||
)
|
||||
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
.requestMatchers("/api/auth/**", "/actuator/health").permitAll()
|
||||
.requestMatchers("/api/admin/**").hasRole("ADMIN")
|
||||
.requestMatchers("/api/users/**").hasAnyRole("USER", "ADMIN")
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.sessionManagement(session -> session
|
||||
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
|
||||
)
|
||||
.exceptionHandling(ex -> ex
|
||||
.authenticationEntryPoint(authenticationEntryPoint())
|
||||
.accessDeniedHandler(accessDeniedHandler())
|
||||
)
|
||||
.addFilterBefore(jwtAuthenticationFilter(),
|
||||
UsernamePasswordAuthenticationFilter.class);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CorsConfigurationSource corsConfigurationSource() {
|
||||
CorsConfiguration configuration = new CorsConfiguration();
|
||||
configuration.setAllowedOrigins(List.of("http://localhost:3000"));
|
||||
configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
|
||||
configuration.setAllowedHeaders(List.of("*"));
|
||||
configuration.setAllowCredentials(true);
|
||||
configuration.setMaxAge(3600L);
|
||||
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/**", configuration);
|
||||
return source;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AuthenticationManager authenticationManager(
|
||||
AuthenticationConfiguration config) throws Exception {
|
||||
return config.getAuthenticationManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder(12);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## JWT Authentication Filter
|
||||
|
||||
```java
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
private final JwtService jwtService;
|
||||
private final UserDetailsService userDetailsService;
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(
|
||||
@NonNull HttpServletRequest request,
|
||||
@NonNull HttpServletRequest response,
|
||||
@NonNull FilterChain filterChain) throws ServletException, IOException {
|
||||
|
||||
final String authHeader = request.getHeader("Authorization");
|
||||
final String jwt;
|
||||
final String username;
|
||||
|
||||
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
jwt = authHeader.substring(7);
|
||||
|
||||
try {
|
||||
username = jwtService.extractUsername(jwt);
|
||||
|
||||
if (username != null && SecurityContextHolder.getContext()
|
||||
.getAuthentication() == null) {
|
||||
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
|
||||
|
||||
if (jwtService.isTokenValid(jwt, userDetails)) {
|
||||
UsernamePasswordAuthenticationToken authToken =
|
||||
new UsernamePasswordAuthenticationToken(
|
||||
userDetails,
|
||||
null,
|
||||
userDetails.getAuthorities()
|
||||
);
|
||||
|
||||
authToken.setDetails(
|
||||
new WebAuthenticationDetailsSource().buildDetails(request)
|
||||
);
|
||||
|
||||
SecurityContextHolder.getContext().setAuthentication(authToken);
|
||||
}
|
||||
}
|
||||
} catch (JwtException e) {
|
||||
log.error("JWT validation failed", e);
|
||||
}
|
||||
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## JWT Service
|
||||
|
||||
```java
|
||||
@Service
|
||||
public class JwtService {
|
||||
@Value("${jwt.secret}")
|
||||
private String secretKey;
|
||||
|
||||
@Value("${jwt.expiration}")
|
||||
private long jwtExpiration;
|
||||
|
||||
@Value("${jwt.refresh-expiration}")
|
||||
private long refreshExpiration;
|
||||
|
||||
public String extractUsername(String token) {
|
||||
return extractClaim(token, Claims::getSubject);
|
||||
}
|
||||
|
||||
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
|
||||
final Claims claims = extractAllClaims(token);
|
||||
return claimsResolver.apply(claims);
|
||||
}
|
||||
|
||||
public String generateToken(UserDetails userDetails) {
|
||||
Map<String, Object> extraClaims = new HashMap<>();
|
||||
extraClaims.put("roles", userDetails.getAuthorities().stream()
|
||||
.map(GrantedAuthority::getAuthority)
|
||||
.collect(Collectors.toList()));
|
||||
|
||||
return generateToken(extraClaims, userDetails);
|
||||
}
|
||||
|
||||
public String generateToken(
|
||||
Map<String, Object> extraClaims,
|
||||
UserDetails userDetails) {
|
||||
return buildToken(extraClaims, userDetails, jwtExpiration);
|
||||
}
|
||||
|
||||
public String generateRefreshToken(UserDetails userDetails) {
|
||||
return buildToken(new HashMap<>(), userDetails, refreshExpiration);
|
||||
}
|
||||
|
||||
private String buildToken(
|
||||
Map<String, Object> extraClaims,
|
||||
UserDetails userDetails,
|
||||
long expiration) {
|
||||
return Jwts
|
||||
.builder()
|
||||
.setClaims(extraClaims)
|
||||
.setSubject(userDetails.getUsername())
|
||||
.setIssuedAt(new Date(System.currentTimeMillis()))
|
||||
.setExpiration(new Date(System.currentTimeMillis() + expiration))
|
||||
.signWith(getSignInKey(), SignatureAlgorithm.HS256)
|
||||
.compact();
|
||||
}
|
||||
|
||||
public boolean isTokenValid(String token, UserDetails userDetails) {
|
||||
final String username = extractUsername(token);
|
||||
return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
|
||||
}
|
||||
|
||||
private boolean isTokenExpired(String token) {
|
||||
return extractExpiration(token).before(new Date());
|
||||
}
|
||||
|
||||
private Date extractExpiration(String token) {
|
||||
return extractClaim(token, Claims::getExpiration);
|
||||
}
|
||||
|
||||
private Claims extractAllClaims(String token) {
|
||||
return Jwts
|
||||
.parserBuilder()
|
||||
.setSigningKey(getSignInKey())
|
||||
.build()
|
||||
.parseClaimsJws(token)
|
||||
.getBody();
|
||||
}
|
||||
|
||||
private Key getSignInKey() {
|
||||
byte[] keyBytes = Decoders.BASE64.decode(secretKey);
|
||||
return Keys.hmacShaKeyFor(keyBytes);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## UserDetailsService Implementation
|
||||
|
||||
```java
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class CustomUserDetailsService implements UserDetailsService {
|
||||
private final UserRepository userRepository;
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||
User user = userRepository.findByEmailWithRoles(username)
|
||||
.orElseThrow(() -> new UsernameNotFoundException(
|
||||
"User not found with email: " + username));
|
||||
|
||||
return org.springframework.security.core.userdetails.User
|
||||
.builder()
|
||||
.username(user.getEmail())
|
||||
.password(user.getPassword())
|
||||
.authorities(user.getRoles().stream()
|
||||
.map(role -> new SimpleGrantedAuthority("ROLE_" + role.getName()))
|
||||
.collect(Collectors.toList()))
|
||||
.accountExpired(false)
|
||||
.accountLocked(!user.getActive())
|
||||
.credentialsExpired(false)
|
||||
.disabled(!user.getActive())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Authentication Controller
|
||||
|
||||
```java
|
||||
@RestController
|
||||
@RequestMapping("/api/auth")
|
||||
@RequiredArgsConstructor
|
||||
public class AuthenticationController {
|
||||
private final AuthenticationService authenticationService;
|
||||
|
||||
@PostMapping("/register")
|
||||
public ResponseEntity<AuthenticationResponse> register(
|
||||
@Valid @RequestBody RegisterRequest request) {
|
||||
AuthenticationResponse response = authenticationService.register(request);
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body(response);
|
||||
}
|
||||
|
||||
@PostMapping("/login")
|
||||
public ResponseEntity<AuthenticationResponse> login(
|
||||
@Valid @RequestBody LoginRequest request) {
|
||||
AuthenticationResponse response = authenticationService.login(request);
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
@PostMapping("/refresh")
|
||||
public ResponseEntity<AuthenticationResponse> refreshToken(
|
||||
@RequestBody RefreshTokenRequest request) {
|
||||
AuthenticationResponse response = authenticationService.refreshToken(request);
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
@PostMapping("/logout")
|
||||
@PreAuthorize("isAuthenticated()")
|
||||
public ResponseEntity<Void> logout() {
|
||||
SecurityContextHolder.clearContext();
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Authentication Service
|
||||
|
||||
```java
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional
|
||||
public class AuthenticationService {
|
||||
private final UserRepository userRepository;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
private final JwtService jwtService;
|
||||
private final AuthenticationManager authenticationManager;
|
||||
|
||||
public AuthenticationResponse register(RegisterRequest request) {
|
||||
if (userRepository.existsByEmail(request.email())) {
|
||||
throw new DuplicateResourceException("Email already registered");
|
||||
}
|
||||
|
||||
User user = User.builder()
|
||||
.email(request.email())
|
||||
.password(passwordEncoder.encode(request.password()))
|
||||
.username(request.username())
|
||||
.active(true)
|
||||
.roles(Set.of(Role.builder().name("USER").build()))
|
||||
.build();
|
||||
|
||||
user = userRepository.save(user);
|
||||
|
||||
String accessToken = jwtService.generateToken(convertToUserDetails(user));
|
||||
String refreshToken = jwtService.generateRefreshToken(convertToUserDetails(user));
|
||||
|
||||
return new AuthenticationResponse(accessToken, refreshToken);
|
||||
}
|
||||
|
||||
public AuthenticationResponse login(LoginRequest request) {
|
||||
authenticationManager.authenticate(
|
||||
new UsernamePasswordAuthenticationToken(
|
||||
request.email(),
|
||||
request.password()
|
||||
)
|
||||
);
|
||||
|
||||
User user = userRepository.findByEmail(request.email())
|
||||
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
|
||||
|
||||
String accessToken = jwtService.generateToken(convertToUserDetails(user));
|
||||
String refreshToken = jwtService.generateRefreshToken(convertToUserDetails(user));
|
||||
|
||||
return new AuthenticationResponse(accessToken, refreshToken);
|
||||
}
|
||||
|
||||
public AuthenticationResponse refreshToken(RefreshTokenRequest request) {
|
||||
String username = jwtService.extractUsername(request.refreshToken());
|
||||
|
||||
User user = userRepository.findByEmail(username)
|
||||
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
|
||||
|
||||
UserDetails userDetails = convertToUserDetails(user);
|
||||
|
||||
if (!jwtService.isTokenValid(request.refreshToken(), userDetails)) {
|
||||
throw new InvalidTokenException("Invalid refresh token");
|
||||
}
|
||||
|
||||
String accessToken = jwtService.generateToken(userDetails);
|
||||
|
||||
return new AuthenticationResponse(accessToken, request.refreshToken());
|
||||
}
|
||||
|
||||
private UserDetails convertToUserDetails(User user) {
|
||||
return org.springframework.security.core.userdetails.User
|
||||
.builder()
|
||||
.username(user.getEmail())
|
||||
.password(user.getPassword())
|
||||
.authorities(user.getRoles().stream()
|
||||
.map(role -> new SimpleGrantedAuthority("ROLE_" + role.getName()))
|
||||
.collect(Collectors.toList()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Method Security
|
||||
|
||||
```java
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class UserService {
|
||||
private final UserRepository userRepository;
|
||||
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
public List<User> getAllUsers() {
|
||||
return userRepository.findAll();
|
||||
}
|
||||
|
||||
@PreAuthorize("hasRole('ADMIN') or #userId == authentication.principal.id")
|
||||
public User getUserById(Long userId) {
|
||||
return userRepository.findById(userId)
|
||||
.orElseThrow(() -> new ResourceNotFoundException("User not found"));
|
||||
}
|
||||
|
||||
@PreAuthorize("isAuthenticated()")
|
||||
@PostAuthorize("returnObject.email == authentication.principal.username")
|
||||
public User updateProfile(Long userId, UserUpdateRequest request) {
|
||||
User user = getUserById(userId);
|
||||
// Update logic
|
||||
return userRepository.save(user);
|
||||
}
|
||||
|
||||
@Secured({"ROLE_ADMIN", "ROLE_MANAGER"})
|
||||
public void deleteUser(Long userId) {
|
||||
userRepository.deleteById(userId);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## OAuth2 Resource Server (JWT)
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class OAuth2ResourceServerConfig {
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
.requestMatchers("/public/**").permitAll()
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.oauth2ResourceServer(oauth2 -> oauth2
|
||||
.jwt(jwt -> jwt
|
||||
.jwtAuthenticationConverter(jwtAuthenticationConverter())
|
||||
)
|
||||
);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public JwtDecoder jwtDecoder() {
|
||||
return JwtDecoders.fromIssuerLocation("https://auth.example.com");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public JwtAuthenticationConverter jwtAuthenticationConverter() {
|
||||
JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter =
|
||||
new JwtGrantedAuthoritiesConverter();
|
||||
grantedAuthoritiesConverter.setAuthoritiesClaimName("roles");
|
||||
grantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");
|
||||
|
||||
JwtAuthenticationConverter jwtAuthenticationConverter =
|
||||
new JwtAuthenticationConverter();
|
||||
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(
|
||||
grantedAuthoritiesConverter);
|
||||
|
||||
return jwtAuthenticationConverter;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Annotation | Purpose |
|
||||
|------------|---------|
|
||||
| `@EnableWebSecurity` | Enables Spring Security |
|
||||
| `@EnableMethodSecurity` | Enables method-level security annotations |
|
||||
| `@PreAuthorize` | Checks authorization before method execution |
|
||||
| `@PostAuthorize` | Checks authorization after method execution |
|
||||
| `@Secured` | Role-based method security |
|
||||
| `@WithMockUser` | Mock authenticated user in tests |
|
||||
| `@AuthenticationPrincipal` | Inject current user in controller |
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
- Always use HTTPS in production
|
||||
- Store JWT secret in environment variables
|
||||
- Use strong password encoding (BCrypt with strength 12+)
|
||||
- Implement token refresh mechanism
|
||||
- Add rate limiting to authentication endpoints
|
||||
- Validate all user inputs
|
||||
- Log security events
|
||||
- Keep dependencies updated
|
||||
- Use CSRF protection for state-changing operations
|
||||
- Implement proper session timeout
|
||||
@@ -1,545 +0,0 @@
|
||||
# Testing - Spring Boot Test
|
||||
|
||||
## Unit Testing with JUnit 5
|
||||
|
||||
```java
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class UserServiceTest {
|
||||
|
||||
@Mock
|
||||
private UserRepository userRepository;
|
||||
|
||||
@Mock
|
||||
private PasswordEncoder passwordEncoder;
|
||||
|
||||
@InjectMocks
|
||||
private UserService userService;
|
||||
|
||||
@Test
|
||||
@DisplayName("Should create user successfully")
|
||||
void shouldCreateUser() {
|
||||
// Given
|
||||
UserCreateRequest request = new UserCreateRequest(
|
||||
"test@example.com",
|
||||
"Password123",
|
||||
"testuser",
|
||||
25
|
||||
);
|
||||
|
||||
User user = User.builder()
|
||||
.id(1L)
|
||||
.email(request.email())
|
||||
.username(request.username())
|
||||
.build();
|
||||
|
||||
when(userRepository.existsByEmail(request.email())).thenReturn(false);
|
||||
when(passwordEncoder.encode(request.password())).thenReturn("encodedPassword");
|
||||
when(userRepository.save(any(User.class))).thenReturn(user);
|
||||
|
||||
// When
|
||||
UserResponse response = userService.create(request);
|
||||
|
||||
// Then
|
||||
assertThat(response).isNotNull();
|
||||
assertThat(response.email()).isEqualTo(request.email());
|
||||
|
||||
verify(userRepository).existsByEmail(request.email());
|
||||
verify(passwordEncoder).encode(request.password());
|
||||
verify(userRepository).save(any(User.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should throw exception when email already exists")
|
||||
void shouldThrowExceptionWhenEmailExists() {
|
||||
// Given
|
||||
UserCreateRequest request = new UserCreateRequest(
|
||||
"test@example.com",
|
||||
"Password123",
|
||||
"testuser",
|
||||
25
|
||||
);
|
||||
|
||||
when(userRepository.existsByEmail(request.email())).thenReturn(true);
|
||||
|
||||
// When & Then
|
||||
assertThatThrownBy(() -> userService.create(request))
|
||||
.isInstanceOf(DuplicateResourceException.class)
|
||||
.hasMessageContaining("Email already registered");
|
||||
|
||||
verify(userRepository, never()).save(any(User.class));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Integration Testing with @SpringBootTest
|
||||
|
||||
```java
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
@ActiveProfiles("test")
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
class UserIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
private TestRestTemplate restTemplate;
|
||||
|
||||
@Autowired
|
||||
private UserRepository userRepository;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
userRepository.deleteAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
@DisplayName("Should create user via API")
|
||||
void shouldCreateUserViaApi() {
|
||||
// Given
|
||||
UserCreateRequest request = new UserCreateRequest(
|
||||
"test@example.com",
|
||||
"Password123",
|
||||
"testuser",
|
||||
25
|
||||
);
|
||||
|
||||
// When
|
||||
ResponseEntity<UserResponse> response = restTemplate.postForEntity(
|
||||
"/api/v1/users",
|
||||
request,
|
||||
UserResponse.class
|
||||
);
|
||||
|
||||
// Then
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED);
|
||||
assertThat(response.getBody()).isNotNull();
|
||||
assertThat(response.getBody().email()).isEqualTo(request.email());
|
||||
assertThat(response.getHeaders().getLocation()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(2)
|
||||
@DisplayName("Should return validation error for invalid request")
|
||||
void shouldReturnValidationError() {
|
||||
// Given
|
||||
UserCreateRequest request = new UserCreateRequest(
|
||||
"invalid-email",
|
||||
"short",
|
||||
"u",
|
||||
15
|
||||
);
|
||||
|
||||
// When
|
||||
ResponseEntity<ValidationErrorResponse> response = restTemplate.postForEntity(
|
||||
"/api/v1/users",
|
||||
request,
|
||||
ValidationErrorResponse.class
|
||||
);
|
||||
|
||||
// Then
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
|
||||
assertThat(response.getBody()).isNotNull();
|
||||
assertThat(response.getBody().errors()).isNotEmpty();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Web Layer Testing with MockMvc
|
||||
|
||||
```java
|
||||
@WebMvcTest(UserController.class)
|
||||
@Import(SecurityConfig.class)
|
||||
class UserControllerTest {
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@MockBean
|
||||
private UserService userService;
|
||||
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@Test
|
||||
@WithMockUser(roles = "ADMIN")
|
||||
@DisplayName("Should get all users")
|
||||
void shouldGetAllUsers() throws Exception {
|
||||
// Given
|
||||
Page<UserResponse> users = new PageImpl<>(List.of(
|
||||
new UserResponse(1L, "user1@example.com", "user1", 25, true, null, null),
|
||||
new UserResponse(2L, "user2@example.com", "user2", 30, true, null, null)
|
||||
));
|
||||
|
||||
when(userService.findAll(any(Pageable.class))).thenReturn(users);
|
||||
|
||||
// When & Then
|
||||
mockMvc.perform(get("/api/v1/users")
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.content").isArray())
|
||||
.andExpect(jsonPath("$.content.length()").value(2))
|
||||
.andExpect(jsonPath("$.content[0].email").value("user1@example.com"))
|
||||
.andDo(print());
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(roles = "ADMIN")
|
||||
@DisplayName("Should create user")
|
||||
void shouldCreateUser() throws Exception {
|
||||
// Given
|
||||
UserCreateRequest request = new UserCreateRequest(
|
||||
"test@example.com",
|
||||
"Password123",
|
||||
"testuser",
|
||||
25
|
||||
);
|
||||
|
||||
UserResponse response = new UserResponse(
|
||||
1L,
|
||||
request.email(),
|
||||
request.username(),
|
||||
request.age(),
|
||||
true,
|
||||
LocalDateTime.now(),
|
||||
LocalDateTime.now()
|
||||
);
|
||||
|
||||
when(userService.create(any(UserCreateRequest.class))).thenReturn(response);
|
||||
|
||||
// When & Then
|
||||
mockMvc.perform(post("/api/v1/users")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.writeValueAsString(request)))
|
||||
.andExpect(status().isCreated())
|
||||
.andExpect(header().exists("Location"))
|
||||
.andExpect(jsonPath("$.email").value(request.email()))
|
||||
.andExpect(jsonPath("$.username").value(request.username()))
|
||||
.andDo(print());
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(roles = "USER")
|
||||
@DisplayName("Should return 403 for non-admin user")
|
||||
void shouldReturn403ForNonAdmin() throws Exception {
|
||||
mockMvc.perform(get("/api/v1/users")
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isForbidden());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Data JPA Testing
|
||||
|
||||
```java
|
||||
@DataJpaTest
|
||||
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
|
||||
@ActiveProfiles("test")
|
||||
class UserRepositoryTest {
|
||||
|
||||
@Autowired
|
||||
private UserRepository userRepository;
|
||||
|
||||
@Autowired
|
||||
private TestEntityManager entityManager;
|
||||
|
||||
@Test
|
||||
@DisplayName("Should find user by email")
|
||||
void shouldFindUserByEmail() {
|
||||
// Given
|
||||
User user = User.builder()
|
||||
.email("test@example.com")
|
||||
.password("password")
|
||||
.username("testuser")
|
||||
.active(true)
|
||||
.build();
|
||||
|
||||
entityManager.persistAndFlush(user);
|
||||
|
||||
// When
|
||||
Optional<User> found = userRepository.findByEmail("test@example.com");
|
||||
|
||||
// Then
|
||||
assertThat(found).isPresent();
|
||||
assertThat(found.get().getEmail()).isEqualTo("test@example.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should check if email exists")
|
||||
void shouldCheckIfEmailExists() {
|
||||
// Given
|
||||
User user = User.builder()
|
||||
.email("test@example.com")
|
||||
.password("password")
|
||||
.username("testuser")
|
||||
.active(true)
|
||||
.build();
|
||||
|
||||
entityManager.persistAndFlush(user);
|
||||
|
||||
// When
|
||||
boolean exists = userRepository.existsByEmail("test@example.com");
|
||||
|
||||
// Then
|
||||
assertThat(exists).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should fetch user with roles")
|
||||
void shouldFetchUserWithRoles() {
|
||||
// Given
|
||||
Role adminRole = Role.builder().name("ADMIN").build();
|
||||
entityManager.persist(adminRole);
|
||||
|
||||
User user = User.builder()
|
||||
.email("admin@example.com")
|
||||
.password("password")
|
||||
.username("admin")
|
||||
.active(true)
|
||||
.roles(Set.of(adminRole))
|
||||
.build();
|
||||
|
||||
entityManager.persistAndFlush(user);
|
||||
entityManager.clear();
|
||||
|
||||
// When
|
||||
Optional<User> found = userRepository.findByEmailWithRoles("admin@example.com");
|
||||
|
||||
// Then
|
||||
assertThat(found).isPresent();
|
||||
assertThat(found.get().getRoles()).hasSize(1);
|
||||
assertThat(found.get().getRoles()).extracting(Role::getName).contains("ADMIN");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Testcontainers for Database
|
||||
|
||||
```java
|
||||
@SpringBootTest
|
||||
@Testcontainers
|
||||
@ActiveProfiles("test")
|
||||
class UserServiceIntegrationTest {
|
||||
|
||||
@Container
|
||||
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15-alpine")
|
||||
.withDatabaseName("testdb")
|
||||
.withUsername("test")
|
||||
.withPassword("test");
|
||||
|
||||
@DynamicPropertySource
|
||||
static void configureProperties(DynamicPropertyRegistry registry) {
|
||||
registry.add("spring.datasource.url", postgres::getJdbcUrl);
|
||||
registry.add("spring.datasource.username", postgres::getUsername);
|
||||
registry.add("spring.datasource.password", postgres::getPassword);
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
|
||||
@Autowired
|
||||
private UserRepository userRepository;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
userRepository.deleteAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should create and find user in real database")
|
||||
void shouldCreateAndFindUser() {
|
||||
// Given
|
||||
UserCreateRequest request = new UserCreateRequest(
|
||||
"test@example.com",
|
||||
"Password123",
|
||||
"testuser",
|
||||
25
|
||||
);
|
||||
|
||||
// When
|
||||
UserResponse created = userService.create(request);
|
||||
UserResponse found = userService.findById(created.id());
|
||||
|
||||
// Then
|
||||
assertThat(found).isNotNull();
|
||||
assertThat(found.email()).isEqualTo(request.email());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Testing Reactive Endpoints with WebTestClient
|
||||
|
||||
```java
|
||||
@WebFluxTest(UserReactiveController.class)
|
||||
class UserReactiveControllerTest {
|
||||
|
||||
@Autowired
|
||||
private WebTestClient webTestClient;
|
||||
|
||||
@MockBean
|
||||
private UserReactiveService userService;
|
||||
|
||||
@Test
|
||||
@DisplayName("Should get user reactively")
|
||||
void shouldGetUserReactively() {
|
||||
// Given
|
||||
UserResponse user = new UserResponse(
|
||||
1L,
|
||||
"test@example.com",
|
||||
"testuser",
|
||||
25,
|
||||
true,
|
||||
LocalDateTime.now(),
|
||||
LocalDateTime.now()
|
||||
);
|
||||
|
||||
when(userService.findById(1L)).thenReturn(Mono.just(user));
|
||||
|
||||
// When & Then
|
||||
webTestClient.get()
|
||||
.uri("/api/v1/users/{id}", 1L)
|
||||
.accept(MediaType.APPLICATION_JSON)
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody(UserResponse.class)
|
||||
.value(response -> {
|
||||
assertThat(response.id()).isEqualTo(1L);
|
||||
assertThat(response.email()).isEqualTo("test@example.com");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should create user reactively")
|
||||
void shouldCreateUserReactively() {
|
||||
// Given
|
||||
UserCreateRequest request = new UserCreateRequest(
|
||||
"test@example.com",
|
||||
"Password123",
|
||||
"testuser",
|
||||
25
|
||||
);
|
||||
|
||||
UserResponse response = new UserResponse(
|
||||
1L,
|
||||
request.email(),
|
||||
request.username(),
|
||||
request.age(),
|
||||
true,
|
||||
LocalDateTime.now(),
|
||||
LocalDateTime.now()
|
||||
);
|
||||
|
||||
when(userService.create(any(UserCreateRequest.class))).thenReturn(Mono.just(response));
|
||||
|
||||
// When & Then
|
||||
webTestClient.post()
|
||||
.uri("/api/v1/users")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.body(Mono.just(request), UserCreateRequest.class)
|
||||
.exchange()
|
||||
.expectStatus().isCreated()
|
||||
.expectHeader().exists("Location")
|
||||
.expectBody(UserResponse.class)
|
||||
.value(user -> {
|
||||
assertThat(user.email()).isEqualTo(request.email());
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Testing Configuration
|
||||
|
||||
```java
|
||||
// application-test.yml
|
||||
spring:
|
||||
datasource:
|
||||
url: jdbc:h2:mem:testdb
|
||||
driver-class-name: org.h2.Driver
|
||||
jpa:
|
||||
hibernate:
|
||||
ddl-auto: create-drop
|
||||
show-sql: true
|
||||
properties:
|
||||
hibernate:
|
||||
format_sql: true
|
||||
security:
|
||||
user:
|
||||
name: test
|
||||
password: test
|
||||
|
||||
logging:
|
||||
level:
|
||||
org.hibernate.SQL: DEBUG
|
||||
org.hibernate.type.descriptor.sql.BasicBinder: TRACE
|
||||
|
||||
// Test Configuration Class
|
||||
@TestConfiguration
|
||||
public class TestConfig {
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder(4); // Faster for tests
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Clock fixedClock() {
|
||||
return Clock.fixed(
|
||||
Instant.parse("2024-01-01T00:00:00Z"),
|
||||
ZoneId.of("UTC")
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Test Fixtures with @DataJpaTest
|
||||
|
||||
```java
|
||||
@Component
|
||||
public class TestDataFactory {
|
||||
|
||||
public static User createUser(String email, String username) {
|
||||
return User.builder()
|
||||
.email(email)
|
||||
.password("encodedPassword")
|
||||
.username(username)
|
||||
.active(true)
|
||||
.createdAt(LocalDateTime.now())
|
||||
.updatedAt(LocalDateTime.now())
|
||||
.build();
|
||||
}
|
||||
|
||||
public static UserCreateRequest createUserRequest() {
|
||||
return new UserCreateRequest(
|
||||
"test@example.com",
|
||||
"Password123",
|
||||
"testuser",
|
||||
25
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Annotation | Purpose |
|
||||
|------------|---------|
|
||||
| `@SpringBootTest` | Full application context integration test |
|
||||
| `@WebMvcTest` | Test MVC controllers with mocked services |
|
||||
| `@WebFluxTest` | Test reactive controllers |
|
||||
| `@DataJpaTest` | Test JPA repositories with in-memory database |
|
||||
| `@MockBean` | Add mock bean to Spring context |
|
||||
| `@WithMockUser` | Mock authenticated user for security tests |
|
||||
| `@Testcontainers` | Enable Testcontainers support |
|
||||
| `@ActiveProfiles` | Activate specific Spring profiles for test |
|
||||
|
||||
## Testing Best Practices
|
||||
|
||||
- Write tests following AAA pattern (Arrange, Act, Assert)
|
||||
- Use descriptive test names with @DisplayName
|
||||
- Mock external dependencies, use real DB with Testcontainers
|
||||
- Achieve 85%+ code coverage
|
||||
- Test happy path and edge cases
|
||||
- Use @Transactional for test data cleanup
|
||||
- Separate unit tests from integration tests
|
||||
- Use parameterized tests for multiple scenarios
|
||||
- Test security rules and validation
|
||||
- Keep tests fast and independent
|
||||
@@ -1,295 +0,0 @@
|
||||
# Web Layer - Controllers & REST APIs
|
||||
|
||||
## REST Controller Pattern
|
||||
|
||||
```java
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/users")
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
public class UserController {
|
||||
private final UserService userService;
|
||||
|
||||
@GetMapping
|
||||
public ResponseEntity<Page<UserResponse>> getUsers(
|
||||
@PageableDefault(size = 20, sort = "createdAt") Pageable pageable) {
|
||||
Page<UserResponse> users = userService.findAll(pageable);
|
||||
return ResponseEntity.ok(users);
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ResponseEntity<UserResponse> getUser(@PathVariable Long id) {
|
||||
UserResponse user = userService.findById(id);
|
||||
return ResponseEntity.ok(user);
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<UserResponse> createUser(
|
||||
@Valid @RequestBody UserCreateRequest request) {
|
||||
UserResponse user = userService.create(request);
|
||||
URI location = ServletUriComponentsBuilder
|
||||
.fromCurrentRequest()
|
||||
.path("/{id}")
|
||||
.buildAndExpand(user.id())
|
||||
.toUri();
|
||||
return ResponseEntity.created(location).body(user);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public ResponseEntity<UserResponse> updateUser(
|
||||
@PathVariable Long id,
|
||||
@Valid @RequestBody UserUpdateRequest request) {
|
||||
UserResponse user = userService.update(id, request);
|
||||
return ResponseEntity.ok(user);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
public void deleteUser(@PathVariable Long id) {
|
||||
userService.delete(id);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Request DTOs with Validation
|
||||
|
||||
```java
|
||||
public record UserCreateRequest(
|
||||
@NotBlank(message = "Email is required")
|
||||
@Email(message = "Email must be valid")
|
||||
String email,
|
||||
|
||||
@NotBlank(message = "Password is required")
|
||||
@Size(min = 8, max = 100, message = "Password must be 8-100 characters")
|
||||
@Pattern(regexp = "^(?=.*[A-Z])(?=.*[a-z])(?=.*\\d).*$",
|
||||
message = "Password must contain uppercase, lowercase, and digit")
|
||||
String password,
|
||||
|
||||
@NotBlank(message = "Username is required")
|
||||
@Size(min = 3, max = 50)
|
||||
@Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "Username must be alphanumeric")
|
||||
String username,
|
||||
|
||||
@Min(value = 18, message = "Must be at least 18")
|
||||
@Max(value = 120, message = "Must be at most 120")
|
||||
Integer age
|
||||
) {}
|
||||
|
||||
public record UserUpdateRequest(
|
||||
@Email(message = "Email must be valid")
|
||||
String email,
|
||||
|
||||
@Size(min = 3, max = 50)
|
||||
String username
|
||||
) {}
|
||||
```
|
||||
|
||||
## Response DTOs
|
||||
|
||||
```java
|
||||
public record UserResponse(
|
||||
Long id,
|
||||
String email,
|
||||
String username,
|
||||
Integer age,
|
||||
Boolean active,
|
||||
LocalDateTime createdAt,
|
||||
LocalDateTime updatedAt
|
||||
) {
|
||||
public static UserResponse from(User user) {
|
||||
return new UserResponse(
|
||||
user.getId(),
|
||||
user.getEmail(),
|
||||
user.getUsername(),
|
||||
user.getAge(),
|
||||
user.getActive(),
|
||||
user.getCreatedAt(),
|
||||
user.getUpdatedAt()
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Global Exception Handling
|
||||
|
||||
```java
|
||||
@RestControllerAdvice
|
||||
@Slf4j
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
@ExceptionHandler(ResourceNotFoundException.class)
|
||||
public ResponseEntity<ErrorResponse> handleNotFound(
|
||||
ResourceNotFoundException ex, WebRequest request) {
|
||||
log.error("Resource not found: {}", ex.getMessage());
|
||||
ErrorResponse error = new ErrorResponse(
|
||||
HttpStatus.NOT_FOUND.value(),
|
||||
ex.getMessage(),
|
||||
request.getDescription(false),
|
||||
LocalDateTime.now()
|
||||
);
|
||||
return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public ResponseEntity<ValidationErrorResponse> handleValidation(
|
||||
MethodArgumentNotValidException ex) {
|
||||
Map<String, String> errors = ex.getBindingResult()
|
||||
.getFieldErrors()
|
||||
.stream()
|
||||
.collect(Collectors.toMap(
|
||||
FieldError::getField,
|
||||
error -> error.getDefaultMessage() != null
|
||||
? error.getDefaultMessage()
|
||||
: "Invalid value"
|
||||
));
|
||||
|
||||
ValidationErrorResponse response = new ValidationErrorResponse(
|
||||
HttpStatus.BAD_REQUEST.value(),
|
||||
"Validation failed",
|
||||
errors,
|
||||
LocalDateTime.now()
|
||||
);
|
||||
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
@ExceptionHandler(DataIntegrityViolationException.class)
|
||||
public ResponseEntity<ErrorResponse> handleDataIntegrity(
|
||||
DataIntegrityViolationException ex, WebRequest request) {
|
||||
log.error("Data integrity violation", ex);
|
||||
ErrorResponse error = new ErrorResponse(
|
||||
HttpStatus.CONFLICT.value(),
|
||||
"Data integrity violation - resource may already exist",
|
||||
request.getDescription(false),
|
||||
LocalDateTime.now()
|
||||
);
|
||||
return new ResponseEntity<>(error, HttpStatus.CONFLICT);
|
||||
}
|
||||
|
||||
@ExceptionHandler(Exception.class)
|
||||
public ResponseEntity<ErrorResponse> handleGlobalException(
|
||||
Exception ex, WebRequest request) {
|
||||
log.error("Unexpected error", ex);
|
||||
ErrorResponse error = new ErrorResponse(
|
||||
HttpStatus.INTERNAL_SERVER_ERROR.value(),
|
||||
"An unexpected error occurred",
|
||||
request.getDescription(false),
|
||||
LocalDateTime.now()
|
||||
);
|
||||
return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
record ErrorResponse(
|
||||
int status,
|
||||
String message,
|
||||
String path,
|
||||
LocalDateTime timestamp
|
||||
) {}
|
||||
|
||||
record ValidationErrorResponse(
|
||||
int status,
|
||||
String message,
|
||||
Map<String, String> errors,
|
||||
LocalDateTime timestamp
|
||||
) {}
|
||||
```
|
||||
|
||||
## Custom Validation
|
||||
|
||||
```java
|
||||
@Target({ElementType.FIELD, ElementType.PARAMETER})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Constraint(validatedBy = UniqueEmailValidator.class)
|
||||
public @interface UniqueEmail {
|
||||
String message() default "Email already exists";
|
||||
Class<?>[] groups() default {};
|
||||
Class<? extends Payload>[] payload() default {};
|
||||
}
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class UniqueEmailValidator implements ConstraintValidator<UniqueEmail, String> {
|
||||
private final UserRepository userRepository;
|
||||
|
||||
@Override
|
||||
public boolean isValid(String email, ConstraintValidatorContext context) {
|
||||
if (email == null) return true;
|
||||
return !userRepository.existsByEmail(email);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## WebClient for External APIs
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
public class WebClientConfig {
|
||||
@Bean
|
||||
public WebClient webClient(WebClient.Builder builder) {
|
||||
return builder
|
||||
.baseUrl("https://api.example.com")
|
||||
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
|
||||
.filter(logRequest())
|
||||
.build();
|
||||
}
|
||||
|
||||
private ExchangeFilterFunction logRequest() {
|
||||
return ExchangeFilterFunction.ofRequestProcessor(request -> {
|
||||
log.info("Request: {} {}", request.method(), request.url());
|
||||
return Mono.just(request);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ExternalApiService {
|
||||
private final WebClient webClient;
|
||||
|
||||
public Mono<ExternalDataResponse> fetchData(String id) {
|
||||
return webClient
|
||||
.get()
|
||||
.uri("/data/{id}", id)
|
||||
.retrieve()
|
||||
.onStatus(HttpStatusCode::is4xxClientError, response ->
|
||||
Mono.error(new ResourceNotFoundException("External resource not found")))
|
||||
.onStatus(HttpStatusCode::is5xxServerError, response ->
|
||||
Mono.error(new ServiceUnavailableException("External service unavailable")))
|
||||
.bodyToMono(ExternalDataResponse.class)
|
||||
.timeout(Duration.ofSeconds(5))
|
||||
.retry(3);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## CORS Configuration
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void addCorsMappings(CorsRegistry registry) {
|
||||
registry.addMapping("/api/**")
|
||||
.allowedOrigins("http://localhost:3000", "https://example.com")
|
||||
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
|
||||
.allowedHeaders("*")
|
||||
.allowCredentials(true)
|
||||
.maxAge(3600);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Annotation | Purpose |
|
||||
|------------|---------|
|
||||
| `@RestController` | Marks class as REST controller (combines @Controller + @ResponseBody) |
|
||||
| `@RequestMapping` | Maps HTTP requests to handler methods |
|
||||
| `@GetMapping/@PostMapping` | HTTP method-specific mappings |
|
||||
| `@PathVariable` | Extracts values from URI path |
|
||||
| `@RequestParam` | Extracts query parameters |
|
||||
| `@RequestBody` | Binds request body to method parameter |
|
||||
| `@Valid` | Triggers validation on request body |
|
||||
| `@RestControllerAdvice` | Global exception handling for REST controllers |
|
||||
| `@ResponseStatus` | Sets HTTP status code for method |
|
||||
@@ -1,63 +0,0 @@
|
||||
-- 为现有的consultation_request表添加缺失的字段
|
||||
DO $$
|
||||
BEGIN
|
||||
-- 检查并添加缺失的字段
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'consultation_request' AND column_name = 'confirming_physician_name') THEN
|
||||
ALTER TABLE consultation_request ADD COLUMN confirming_physician_name VARCHAR(100);
|
||||
COMMENT ON COLUMN consultation_request.confirming_physician_name IS '确认会诊的医生姓名';
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'consultation_request' AND column_name = 'confirming_department_name') THEN
|
||||
ALTER TABLE consultation_request ADD COLUMN confirming_department_name VARCHAR(100);
|
||||
COMMENT ON COLUMN consultation_request.confirming_department_name IS '代表科室';
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'consultation_request' AND column_name = 'confirming_physician_participation') THEN
|
||||
ALTER TABLE consultation_request ADD COLUMN confirming_physician_participation VARCHAR(100);
|
||||
COMMENT ON COLUMN consultation_request.confirming_physician_participation IS '会诊确认参加医师';
|
||||
END IF;
|
||||
|
||||
-- 检查并更新consultation_status字段的注释
|
||||
DROP COMMENT ON COLUMN consultation_request.consultation_status;
|
||||
COMMENT ON COLUMN consultation_request.consultation_status IS '会诊状态:0-新开,10-已提交,20-已确认,30-已签名,40-已完成,50-已取消';
|
||||
|
||||
-- 检查并更新consultation_urgency字段的默认值
|
||||
ALTER TABLE consultation_request ALTER COLUMN consultation_urgency SET DEFAULT '一般';
|
||||
|
||||
-- 检查并更新字段注释
|
||||
COMMENT ON COLUMN consultation_request.consultation_id IS '会诊申请单号:CS+年月日时分秒+4位随机数';
|
||||
COMMENT ON COLUMN consultation_request.consultation_request_date IS '会诊申请时间';
|
||||
COMMENT ON COLUMN consultation_request.consultation_date IS '会诊时间';
|
||||
COMMENT ON COLUMN consultation_request.consultation_purpose IS '简要病史及会诊目的';
|
||||
COMMENT ON COLUMN consultation_request.consultation_opinion IS '会诊意见';
|
||||
COMMENT ON COLUMN consultation_request.consultation_status IS '会诊状态:0-新开,10-已提交,20-已确认,30-已签名,40-已完成,50-已取消';
|
||||
COMMENT ON COLUMN consultation_request.consultation_urgency IS '是否紧急:一般/紧急';
|
||||
COMMENT ON COLUMN consultation_request.confirming_physician IS '提交会诊的医生姓名';
|
||||
COMMENT ON COLUMN consultation_request.confirming_physician_id IS '提交会诊的医生ID';
|
||||
COMMENT ON COLUMN consultation_request.confirming_date IS '提交会诊日期';
|
||||
COMMENT ON COLUMN consultation_request.signature IS '结束会诊医生姓名/签名医生';
|
||||
COMMENT ON COLUMN consultation_request.signature_physician_id IS '结束会诊医生ID';
|
||||
COMMENT ON COLUMN consultation_request.signature_date IS '结束会诊日期/签名时间';
|
||||
COMMENT ON COLUMN consultation_request.cancel_nature_date IS '作废会诊日期';
|
||||
COMMENT ON COLUMN consultation_request.cancel_reason IS '作废原因';
|
||||
COMMENT ON COLUMN consultation_request.consultation_activity_id IS '会诊项目ID,关联wor_activity_definition表,用于确定会诊类型和价格';
|
||||
COMMENT ON COLUMN consultation_request.consultation_activity_name IS '会诊项目名称,如:院内会诊、远程会诊等';
|
||||
|
||||
END $$;
|
||||
|
||||
-- 为现有表添加必要的索引
|
||||
DO $$
|
||||
BEGIN
|
||||
-- 检查并创建索引
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_indexes WHERE tablename = 'consultation_request' AND indexname = 'idx_consultation_request_status_date') THEN
|
||||
CREATE INDEX idx_consultation_request_status_date ON consultation_request (consultation_status, consultation_request_date);
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_indexes WHERE tablename = 'consultation_request' AND indexname = 'idx_consultation_request_physician') THEN
|
||||
CREATE INDEX idx_consultation_request_physician ON consultation_request (requesting_physician_id);
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_indexes WHERE tablename = 'consultation_request' AND indexname = 'idx_consultation_request_invited_object') THEN
|
||||
CREATE INDEX idx_consultation_request_invited_object ON consultation_request (invited_object);
|
||||
END IF;
|
||||
END $$;
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.core.common.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* Excel额外表头信息注解
|
||||
@@ -14,7 +15,7 @@ public @interface ExcelExtra {
|
||||
/**
|
||||
* 表头名称
|
||||
*/
|
||||
String name();
|
||||
String name() default "";
|
||||
|
||||
/**
|
||||
* 日期格式,如:yyyy-MM-dd HH:mm:ss
|
||||
@@ -35,4 +36,15 @@ public @interface ExcelExtra {
|
||||
* 是否导出
|
||||
*/
|
||||
boolean isExport() default true;
|
||||
|
||||
/**
|
||||
* 精度 默认:-1(默认不开启BigDecimal格式化)
|
||||
*/
|
||||
int scale() default -1;
|
||||
|
||||
/**
|
||||
* BigDecimal 舍入规则 默认:BigDecimal.ROUND_HALF_EVEN
|
||||
*/
|
||||
int roundingMode() default BigDecimal.ROUND_HALF_EVEN;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.core.common.enums;
|
||||
|
||||
/**
|
||||
* 角色枚举
|
||||
*
|
||||
* @author swb
|
||||
* @date 2026-01-29
|
||||
*/
|
||||
public enum RoleEnum {
|
||||
DOCTOR("doctor", "医生"),
|
||||
NURSE("nurse", "护士"),
|
||||
ADMIN("admin", "管理员");
|
||||
private final String code;
|
||||
private final String info;
|
||||
|
||||
RoleEnum(String code, String info) {
|
||||
this.code = code;
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getInfo() {
|
||||
return info;
|
||||
}
|
||||
}
|
||||
@@ -33,67 +33,125 @@ public final class AgeCalculatorUtil {
|
||||
return period.getYears();
|
||||
}
|
||||
|
||||
// /**
|
||||
// * 当前年龄取得(床位列表表示年龄用)
|
||||
// */
|
||||
// public static String getAge(Date date) {
|
||||
// // 将 Date 转换为 LocalDateTime
|
||||
// LocalDateTime dateTime = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
|
||||
// LocalDateTime now = LocalDateTime.now();
|
||||
// int years = now.getYear() - dateTime.getYear();
|
||||
// if (years > 2) {
|
||||
// return String.format("%d岁", years);
|
||||
// }
|
||||
//
|
||||
// Period period = Period.between(dateTime.toLocalDate(), now.toLocalDate());
|
||||
// int months = period.getMonths();
|
||||
// int days = period.getDays();
|
||||
// long hours = ChronoUnit.HOURS.between(dateTime, now) - (days * 24L);
|
||||
//
|
||||
// if (hours < 0) {
|
||||
// hours += 24;
|
||||
// days--;
|
||||
// }
|
||||
// if (days < 0) {
|
||||
// months--;
|
||||
// days = getLastDayOfMonth(dateTime) - dateTime.getDayOfMonth() + now.getDayOfMonth();
|
||||
// }
|
||||
// if (months < 0) {
|
||||
// months += 12;
|
||||
// years--;
|
||||
// }
|
||||
// if (years < 0) {
|
||||
// return "1小时";
|
||||
// }
|
||||
//
|
||||
// if (years > 0 && months > 0) {
|
||||
// return String.format("%d岁%d月", years, months);
|
||||
// }
|
||||
// if (years > 0) {
|
||||
// return String.format("%d岁", years);
|
||||
// }
|
||||
// if (months > 0 && days > 0) {
|
||||
// return String.format("%d月%d天", months, days);
|
||||
// }
|
||||
// if (months > 0) {
|
||||
// return String.format("%d月", months);
|
||||
// }
|
||||
// if (days > 0 && hours > 0) {
|
||||
// return String.format("%d天%d小时", days, hours);
|
||||
// }
|
||||
// if (days > 0) {
|
||||
// return String.format("%d天", days);
|
||||
// }
|
||||
// if (hours > 0) {
|
||||
// return String.format("%d小时", hours);
|
||||
// }
|
||||
// return "1小时";
|
||||
// }
|
||||
/**
|
||||
* 当前年龄取得(床位列表表示年龄用)
|
||||
* 复刻Oracle函数FUN_GET_AGE的核心逻辑:返回年龄字符串
|
||||
*
|
||||
* @param birthDate 出生日期
|
||||
* @return 年龄字符串(如:29岁、3岁5月、2月15天、18天),出生日期晚于当前日期返回空字符串
|
||||
*/
|
||||
public static String getAge(Date date) {
|
||||
// 添加空值检查
|
||||
if (date == null) {
|
||||
public static String getAge(Date birthDate) {
|
||||
// 入参校验
|
||||
if (birthDate == null) {
|
||||
return "";
|
||||
}
|
||||
// 将 Date 转换为 LocalDateTime
|
||||
LocalDateTime dateTime = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
int years = now.getYear() - dateTime.getYear();
|
||||
if (years > 2) {
|
||||
return String.format("%d岁", years);
|
||||
|
||||
// 将Date转换为LocalDate(使用系统默认时区)
|
||||
LocalDate birthLocalDate = birthDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
|
||||
LocalDate currentDate = LocalDate.now();
|
||||
|
||||
// 计算总天数(对应Oracle中的IDAY)
|
||||
long totalDays = ChronoUnit.DAYS.between(birthLocalDate, currentDate);
|
||||
|
||||
// 若出生日期晚于当前日期,返回空字符串
|
||||
if (totalDays < 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
Period period = Period.between(dateTime.toLocalDate(), now.toLocalDate());
|
||||
int months = period.getMonths();
|
||||
int days = period.getDays();
|
||||
long hours = ChronoUnit.HOURS.between(dateTime, now) - (days * 24L);
|
||||
// 计算年份(复刻Oracle的闰年补偿逻辑:(当前年-出生年)/4 补偿闰年天数)
|
||||
int birthYear = birthLocalDate.getYear();
|
||||
int currentYear = currentDate.getYear();
|
||||
long leapYearCompensation = (currentYear - birthYear) / 4;
|
||||
long adjustedDays = totalDays - leapYearCompensation;
|
||||
|
||||
if (hours < 0) {
|
||||
hours += 24;
|
||||
days--;
|
||||
}
|
||||
if (days < 0) {
|
||||
months--;
|
||||
days = getLastDayOfMonth(dateTime) - dateTime.getDayOfMonth() + now.getDayOfMonth();
|
||||
}
|
||||
if (months < 0) {
|
||||
months += 12;
|
||||
years--;
|
||||
}
|
||||
if (years < 0) {
|
||||
return "1小时";
|
||||
}
|
||||
// 计算年、月、天(按365天/年、30天/月粗略折算,与Oracle逻辑一致)
|
||||
int iYear = (int) (adjustedDays / 365);
|
||||
long remainingDaysAfterYear = adjustedDays - iYear * 365;
|
||||
int iMonth = (int) (remainingDaysAfterYear / 30);
|
||||
int iDay = (int) (remainingDaysAfterYear - iMonth * 30);
|
||||
|
||||
if (years > 0 && months > 0) {
|
||||
return String.format("%d岁%d月", years, months);
|
||||
// 按原函数规则拼接返回字符串
|
||||
if (iYear <= 0) {
|
||||
// 小于1岁
|
||||
if (iMonth <= 0) {
|
||||
// 小于1个月,返回X天
|
||||
return iDay + "天";
|
||||
} else {
|
||||
// 1个月及以上,返回X月X天
|
||||
return iMonth + "月" + iDay + "天";
|
||||
}
|
||||
} else {
|
||||
// 1岁及以上
|
||||
if (iYear < 5) {
|
||||
// 1-4岁
|
||||
if (iMonth <= 0) {
|
||||
// 无整月,返回X岁X天
|
||||
return iYear + "岁" + iDay + "天";
|
||||
} else {
|
||||
// 有整月,返回X岁X月
|
||||
return iYear + "岁" + iMonth + "月";
|
||||
}
|
||||
} else {
|
||||
// 5岁及以上,仅返回X岁
|
||||
return iYear + "岁";
|
||||
}
|
||||
}
|
||||
if (years > 0) {
|
||||
return String.format("%d岁", years);
|
||||
}
|
||||
if (months > 0 && days > 0) {
|
||||
return String.format("%d月%d天", months, days);
|
||||
}
|
||||
if (months > 0) {
|
||||
return String.format("%d月", months);
|
||||
}
|
||||
if (days > 0 && hours > 0) {
|
||||
return String.format("%d天%d小时", days, hours);
|
||||
}
|
||||
if (days > 0) {
|
||||
return String.format("%d天", days);
|
||||
}
|
||||
if (hours > 0) {
|
||||
return String.format("%d小时", hours);
|
||||
}
|
||||
return "1小时";
|
||||
}
|
||||
|
||||
private static int getLastDayOfMonth(LocalDateTime dateTime) {
|
||||
int[] daysInMonth = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
|
||||
if (isLeapYear(dateTime.getYear()) && dateTime.getMonthValue() == 2) {
|
||||
|
||||
@@ -2,6 +2,8 @@ package com.core.common.utils;
|
||||
|
||||
import com.core.common.core.domain.model.LoginUser;
|
||||
import com.core.common.utils.SecurityUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
@@ -14,6 +16,8 @@ import java.util.Date;
|
||||
@Component
|
||||
public class AuditFieldUtil {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(AuditFieldUtil.class);
|
||||
|
||||
/**
|
||||
* 为实体设置创建相关的审计字段
|
||||
* @param entity 实体对象
|
||||
@@ -65,8 +69,7 @@ public class AuditFieldUtil {
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("设置创建审计字段时发生异常: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
log.error("设置创建审计字段时发生异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,8 +113,7 @@ public class AuditFieldUtil {
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("设置更新审计字段时发生异常: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
log.error("设置更新审计字段时发生异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.core.common.utils;
|
||||
|
||||
import org.apache.commons.lang3.time.DateFormatUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.text.ParseException;
|
||||
@@ -17,6 +19,9 @@ import java.util.Date;
|
||||
* @author system
|
||||
*/
|
||||
public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(DateUtils.class);
|
||||
|
||||
public static String YYYY = "yyyy";
|
||||
|
||||
public static String YYYY_MM = "yyyy-MM";
|
||||
@@ -227,7 +232,7 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
|
||||
return endTime;
|
||||
}
|
||||
} catch (DateTimeParseException e) {
|
||||
e.printStackTrace();
|
||||
log.warn("日期解析失败: {}", strDate, e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -250,7 +255,7 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
|
||||
// 检查日期是否是未来的时间
|
||||
return dateToCheck.isAfter(currentDate);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
log.warn("日期解析失败: {}", dateString, e);
|
||||
// 解析失败或其他异常,返回 false 或根据需要处理异常
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
package com.core.common.utils;
|
||||
|
||||
import com.core.common.annotation.Excel;
|
||||
import com.core.common.annotation.Excel.ColumnType;
|
||||
import com.core.common.annotation.Excel.Type;
|
||||
import com.core.common.annotation.ExcelExtra;
|
||||
import com.core.common.annotation.Excels;
|
||||
import com.core.common.config.CoreConfig;
|
||||
import com.core.common.core.domain.AjaxResult;
|
||||
import com.core.common.core.text.Convert;
|
||||
import com.core.common.exception.UtilException;
|
||||
import com.core.common.utils.file.FileTypeUtils;
|
||||
import com.core.common.utils.file.FileUtils;
|
||||
import com.core.common.utils.file.ImageUtils;
|
||||
import com.core.common.utils.poi.ExcelHandlerAdapter;
|
||||
import com.core.common.utils.reflect.ReflectUtils;
|
||||
import java.io.*;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.math.BigDecimal;
|
||||
import java.text.DecimalFormat;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.apache.commons.lang3.RegExUtils;
|
||||
import org.apache.commons.lang3.reflect.FieldUtils;
|
||||
@@ -30,17 +29,20 @@ import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTMarker;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.*;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.math.BigDecimal;
|
||||
import java.text.DecimalFormat;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import com.core.common.annotation.Excel;
|
||||
import com.core.common.annotation.Excel.ColumnType;
|
||||
import com.core.common.annotation.Excel.Type;
|
||||
import com.core.common.annotation.ExcelExtra;
|
||||
import com.core.common.annotation.Excels;
|
||||
import com.core.common.config.CoreConfig;
|
||||
import com.core.common.core.domain.AjaxResult;
|
||||
import com.core.common.core.text.Convert;
|
||||
import com.core.common.exception.UtilException;
|
||||
import com.core.common.utils.file.FileTypeUtils;
|
||||
import com.core.common.utils.file.FileUtils;
|
||||
import com.core.common.utils.file.ImageUtils;
|
||||
import com.core.common.utils.poi.ExcelHandlerAdapter;
|
||||
import com.core.common.utils.reflect.ReflectUtils;
|
||||
|
||||
/**
|
||||
* Excel相关处理
|
||||
@@ -1164,6 +1166,11 @@ public class NewExcelUtil<T> {
|
||||
ParameterizedType pt = (ParameterizedType)field.getGenericType();
|
||||
Class<?> subClass = (Class<?>)pt.getActualTypeArguments()[0];
|
||||
this.subFields = FieldUtils.getFieldsListWithAnnotation(subClass, Excel.class);
|
||||
if (StringUtils.isNotEmpty(includeFields)) {
|
||||
this.subFields = this.subFields.stream().filter(f -> ArrayUtils.contains(includeFields, f.getName())).collect(Collectors.toList());
|
||||
} else if (StringUtils.isNotEmpty(excludeFields)) {
|
||||
this.subFields = this.subFields.stream().filter(f -> !ArrayUtils.contains(excludeFields, f.getName())).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1441,7 +1448,28 @@ public class NewExcelUtil<T> {
|
||||
}
|
||||
|
||||
try {
|
||||
// 计算表格体总列数
|
||||
int totalCols = 0;
|
||||
for (Object[] os : fields) {
|
||||
Field field = (Field)os[0];
|
||||
if (Collection.class.isAssignableFrom(field.getType()) && subFields != null) {
|
||||
long subCount = subFields.stream().filter(f -> f.isAnnotationPresent(Excel.class)).count();
|
||||
totalCols += subCount;
|
||||
} else {
|
||||
totalCols++;
|
||||
}
|
||||
}
|
||||
if (totalCols == 0) totalCols = 1;
|
||||
|
||||
int currentRowNum = rownum;
|
||||
int colIndex = 0;
|
||||
Row row = null;
|
||||
boolean hasVisible = false;
|
||||
|
||||
// 布局配置:Label占用1列,Value占用2列,共3列
|
||||
int labelCols = 1;
|
||||
int valueCols = 2;
|
||||
int itemCols = labelCols + valueCols;
|
||||
|
||||
for (Object[] os : extraFields) {
|
||||
Field field = (Field)os[0];
|
||||
@@ -1451,43 +1479,50 @@ public class NewExcelUtil<T> {
|
||||
if (isExtraFieldHidden(field.getName())) {
|
||||
continue;
|
||||
}
|
||||
hasVisible = true;
|
||||
|
||||
Row row = sheet.createRow(currentRowNum++);
|
||||
// 自动换行:如果不是行首,且剩余空间不足,则换行
|
||||
if (row == null) {
|
||||
row = sheet.createRow(currentRowNum);
|
||||
} else if (colIndex > 0 && colIndex + itemCols > totalCols) {
|
||||
currentRowNum++;
|
||||
row = sheet.createRow(currentRowNum);
|
||||
colIndex = 0;
|
||||
}
|
||||
|
||||
// 创建标签单元格(第0列)
|
||||
Cell labelCell = row.createCell(0);
|
||||
// 1. 创建 Label 单元格
|
||||
Cell labelCell = row.createCell(colIndex);
|
||||
labelCell.setCellValue(attr.name());
|
||||
labelCell.setCellStyle(styles.get("extraLabel"));
|
||||
|
||||
// 创建值单元格(第1列)
|
||||
Cell valueCell = row.createCell(1);
|
||||
// 2. 创建 Value 单元格
|
||||
int valueStartCol = colIndex + labelCols;
|
||||
Cell valueCell = row.createCell(valueStartCol);
|
||||
Object value = field.get(entity);
|
||||
String cellValue = formatExtraCellValue(value, attr);
|
||||
valueCell.setCellValue(StringUtils.isNull(cellValue) ? attr.defaultValue() : cellValue);
|
||||
valueCell.setCellStyle(styles.get("extraValue"));
|
||||
|
||||
// 创建合并区域(第1列到第2列)
|
||||
CellRangeAddress mergedRegion = new CellRangeAddress(row.getRowNum(), row.getRowNum(), 1, 2);
|
||||
sheet.addMergedRegion(mergedRegion);
|
||||
// 3. 合并 Value 单元格
|
||||
if (valueCols > 1) {
|
||||
int valueEndCol = valueStartCol + valueCols - 1;
|
||||
CellRangeAddress mergedRegion = new CellRangeAddress(row.getRowNum(), row.getRowNum(), valueStartCol, valueEndCol);
|
||||
sheet.addMergedRegion(mergedRegion);
|
||||
|
||||
// 手动设置合并区域的边框,确保完整显示
|
||||
RegionUtil.setBorderTop(BorderStyle.THIN, mergedRegion, sheet);
|
||||
RegionUtil.setBorderBottom(BorderStyle.THIN, mergedRegion, sheet);
|
||||
RegionUtil.setBorderLeft(BorderStyle.THIN, mergedRegion, sheet);
|
||||
RegionUtil.setBorderRight(BorderStyle.THIN, mergedRegion, sheet);
|
||||
RegionUtil.setTopBorderColor(IndexedColors.BLACK.getIndex(), mergedRegion, sheet);
|
||||
RegionUtil.setBottomBorderColor(IndexedColors.BLACK.getIndex(), mergedRegion, sheet);
|
||||
RegionUtil.setLeftBorderColor(IndexedColors.BLACK.getIndex(), mergedRegion, sheet);
|
||||
RegionUtil.setRightBorderColor(IndexedColors.BLACK.getIndex(), mergedRegion, sheet);
|
||||
// 设置边框
|
||||
RegionUtil.setBorderTop(BorderStyle.THIN, mergedRegion, sheet);
|
||||
RegionUtil.setBorderBottom(BorderStyle.THIN, mergedRegion, sheet);
|
||||
RegionUtil.setBorderLeft(BorderStyle.THIN, mergedRegion, sheet);
|
||||
RegionUtil.setBorderRight(BorderStyle.THIN, mergedRegion, sheet);
|
||||
}
|
||||
|
||||
colIndex += itemCols;
|
||||
}
|
||||
|
||||
// 设置列宽
|
||||
sheet.setColumnWidth(0, 15 * 256); // 标签列宽
|
||||
sheet.setColumnWidth(1, 20 * 256); // 值列宽
|
||||
sheet.setColumnWidth(2, 20 * 256); // 值列宽
|
||||
|
||||
// 更新当前行号,在额外表头和数据表头之间空一行
|
||||
rownum = currentRowNum + 1;
|
||||
if (hasVisible) {
|
||||
rownum = currentRowNum + 2;
|
||||
}
|
||||
subMergedFirstRowNum = rownum;
|
||||
subMergedLastRowNum = rownum;
|
||||
|
||||
@@ -1508,6 +1543,10 @@ public class NewExcelUtil<T> {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (value instanceof BigDecimal && attr.scale() >= 0) {
|
||||
return ((BigDecimal) value).setScale(attr.scale(), attr.roundingMode()).toString();
|
||||
}
|
||||
|
||||
if (StringUtils.isNotEmpty(attr.dateFormat())) {
|
||||
return parseDateToStr(attr.dateFormat(), value);
|
||||
}
|
||||
@@ -1808,7 +1847,7 @@ public class NewExcelUtil<T> {
|
||||
row = sheet.createRow(rowNo);
|
||||
}
|
||||
// 子字段也要排序
|
||||
List<Field> subFields = FieldUtils.getFieldsListWithAnnotation(obj.getClass(), Excel.class);
|
||||
List<Field> subFields = this.subFields;
|
||||
List<Field> sortedSubFields = subFields.stream().sorted(Comparator.comparing(subField -> {
|
||||
Excel subExcel = subField.getAnnotation(Excel.class);
|
||||
return subExcel.sort();
|
||||
|
||||
@@ -2,6 +2,8 @@ package com.core.common.utils;
|
||||
|
||||
import com.core.common.constant.Constants;
|
||||
import com.core.common.core.text.Convert;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.web.context.request.RequestAttributes;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
@@ -24,6 +26,9 @@ import java.util.Map;
|
||||
* @author system
|
||||
*/
|
||||
public class ServletUtils {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ServletUtils.class);
|
||||
|
||||
/**
|
||||
* 获取String参数
|
||||
*/
|
||||
@@ -130,7 +135,7 @@ public class ServletUtils {
|
||||
response.setCharacterEncoding("utf-8");
|
||||
response.getWriter().print(string);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
log.error("渲染响应字符串失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package com.core.common.utils.bean;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -12,6 +15,9 @@ import java.util.regex.Pattern;
|
||||
* @author system
|
||||
*/
|
||||
public class BeanUtils extends org.springframework.beans.BeanUtils {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(BeanUtils.class);
|
||||
|
||||
/**
|
||||
* Bean方法名中属性名开始的下标
|
||||
*/
|
||||
@@ -37,7 +43,7 @@ public class BeanUtils extends org.springframework.beans.BeanUtils {
|
||||
try {
|
||||
copyProperties(src, dest);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
log.error("Bean属性复制失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user