Apogee Home

Apogée XForms Engine: developer guide

API, examples, implementation details, etc.

Bogdan Stefanescu, bs@nuxeo.com, Nuxeo


1. Overview

This document describes the implementation of the XForms plug-in for Eclipse.

The XForms engine is generating SWT forms from an input XForms file and optional XML Schema and CSS style sheet files. Only a subset of the XForms standard is supported for now (more feature will be added in future) which is detailed on XForms Implementation section.

The XML Schema is supported for automatically validation of the generated forms. You can find more details about XML Schema support are in XML Schema Support section.

The style sheet files are adapted to describes SWT widgets so that they are not compatible with HTML style sheets. Style sheets are described in SWT Style Sheets section

In the last section of the document there are provided some examples on how to use the API. Anyway the API is still experimental and may change in the future.

2. Known Limitations

XForms - There are several limitations on the XForms implementation. Instead of listing what is not supported we describe every implemented XForms concept and what limitation it have if any. For more details see the XForms Implementation section.

XML Schema - The XML Schema engine is not completely implementing the XML Schema specifications and it is not designed to do this --- TODO ---. It was designed only to validate user input for the document fields it changes. It is not serving to validate entire XML documents (including document structure) but only data types and constraints. The XML schema concepts that are not supported (like complexType) are ignored. Only the information on the element types and constraints are read from the XSD file. For information on every supported concept and it's limitations see the section XML Schema Implementation

CSS - The Style Sheets implementation is not supporting inheritance and multiple styles defined on the same line (like input, label { ... } ) - this will change in the future.

3. XForms Implementation

The XForms engine is ignoring any unknown tag or XForms concept it doesn't understand. So even if some concepts are not yet implemented the form is correctly displayed with the only limitation that unimplemented XForms concepts will not be available.

XForms is not an XML document instead it is living inside other XML container documents like XHTML. The container document is ignored by the XForms engine and only the XForms elements are extracted and interpreted. So you may write your form inside any XML document you want, the form will be correctly interpreted. The only requirement is that the document containing the form should be a valid XML document (i.e. containing a root element).

Any element in the XForms document may contains the attributes id, class and style.

XForms control may be declared anywhere in the host document but when using the SWT generator it is recommended to use a top level group or a XHTML body element as a top level control container. This way you can control the style of the SWT composite, otherwise the controls will be put as is in the parent composite specified at runtime and you cannot control scrolling and stuff like this. The best is to use a XHTML body container because this is by default mapped to a ScrolledForm control.

References to external documents such style sheets and XML schema may be specified as absolute URLs or as relative paths to the host document

The most important limitations of the current implementation are:

  1. Binding is working only on instance elements and not to instance element attributes. Support for attributes binding will be added in the future.

  2. Only a part of the XForms event module is implemented

  3. XForms types and constraints are not implemented. The only way to define types and constraints on elements values is XML Schema

  4. The dynamic behaviors described by XForms are not implemented

3.1. XML Namespace Handling

The XForms engine is doing non-restrictive namespace checks on elements. This means elements that are not in any namespace are interpreted as XForms elements if they match an XForms tag. So for example if no default namespace was declared then the following tag declaration is recognized as a valid XForms input tag even if it's not decalred to be from XForms namespace:

<input id=”theInput” ref=”/document/title”> .. </input>.

Attributes are not checked for namespace. So, inside a XForms tag, if an attribute match a XForms attribute name that is valid for the tag then it will be considered a XForms attribute. Thus, the element namespace is inherited by attributes if no specific namespace is declared at attribute level.

The following “ref” attributes are recognized as valid XForms attributes:

  1. <xforms:input xforms:ref=””/>

  2. <xforms:input ref=””/>

3.2. XHTML Support

By convenience there are three XHTML tags interpreted by the XForms engine.

These elements should be inside the XHTML namespace, but for convenience these elements are also recognized in the XForms namespace or if they are not declared inside a namespace.

  1. style – the style element is used to specify an in-line text/css stylesheet

    <style type=”text/css”> ... </style>CSS Style Sheets may also be specified inside a processing instruction

    <?stylesheet ... ?>

  2. link – the body tag is used to specify external documents linked to the current one. There are two types of documents that can be specified:

  3. CSS Style-Sheets:

    <xhtml:link rel=”StyleSheet” type=”text/css” href=”document.css”/>

    Multiple style sheets may be loaded this way

  4. XML Schema:

    <xhtml:link rel=”Schema” type=”xml/schema” href=”document.xsd”/>

    The schema loaded in this way is the default XForms schema so that it will be used by any model not declaring a schema. You should not declare multiple schema this way otherwise only the last declared schema will be set as the default schema.

  5. body – the body tag is interpreted by the UI generator as the top level container for controls. Thus, the SWT UI generator will create for this tag a ScrolledForm. The default behavior can be changed using styles.

    This is done to generate the form from XHTML documents and without using SWT style sheets.

3.3. XForms Model

3.3.1. Model Element

You may declare several models in your XForms document. Each model may have the following attributes:

  1. id – the model ID

  2. schema – a link to a XML Schema document to be used for the model

A model is supporting a single instance (and not multiple instances as in XForms specifications). Also it may contains several bind and submission elements.

XForms Actions are ignored in the model tag and children.

3.3.2. Instance Element

May contains the document model or a reference to an external instance document.

Attributes:

  1. id – the instance id. Anyway this is ignored since only one instance is supported

  2. src – URL to an external document instance

3.3.3. Bind Element

The bind element is ignoring for now any other attribute than id, nodeset and type.

Attributes:

  1. id – the bind ID

  2. nodeset – the XPath expression to select nodes from the model instance. Only element nodes are supported for now

  3. type – the XSD type to be applied on the selected nodes. Should be of the form: type=”prefix:type” where prefix is the prefix of the namespace where the type is defined. Example: type=”xsd:string” where xsd was bound to XML Schema namespace.

3.3.4. Submission Element

Supported attributes:

  1. id – the submission ID (required to be referred from a submit control)

  2. action – the submission action. Action currently supported are http:// and fille:// URLs and two special actions for debugging purposes: stout and stderr (that will print the submitted document on STDOUT respectively STDERR)

  3. method – the submit method. Depends on the action handler.

  4. indent (true/false)

  5. encoding

  6. omit-xml-declaration (true/false)

  7. includenamespaceprefixes (true/false)

3.4. XForms Controls

All controls may contains the following attributes:

  1. id - the control ID – used to uniquely identify the control in the document. Can be used in style sheets or from the API to retrieve the control object

  2. class – used to specify the style sheet class

  3. style – can be used to specify an in-line defined style

  4. bind – the ID of a declared bind element. ID are uniques in the file so no model ID is needed

  5. model – the model ID to be used when binding using the “ref” attribute

  6. ref – the XPath expression to select a node from the document instance of the model specified by “model”. Only elements nodes are supported for now. If more nodes are selected the binding is done on the first node from the selection node set.

There is the list of the implemented controls:

  1. group

  2. input

  3. textarea

  4. label

  5. hint

  6. select

    1. item

      1. value

  7. select1

    1. item

      1. value

  8. trigger

  9. submit

  10. secret

  11. output

There is a limitation on “select” and “select1” controls: only item and values are supported. The itemset element is not yet supported.

4. XForms Actions and Events

The following actions are implemented:

  1. action

  2. disptach

  3. rebuild (currently, it does nothing)

  4. recalculate

  5. revalidate

  6. refresh (currently it does nothing)

  7. reset

The following XForms events are supported:

  1. xforms-value-changed

  2. xforms-valid

  3. xforms-invalid

  4. xforms-recalculate

  5. xforms-refresh

  6. xforms-reset

  7. xforms-revalidate

  8. xforms-submit

  9. xforms-binding-exception

  10. xforms-submit-done

  11. xforms-submit-error

The following (DOM) events are generated by the SWT XForms implementation and can be used in the XForms document as events that trigger an action:

  1. click

  2. doubleclick

  3. mouseup

  4. mousedown

  5. DOMFocusIn

  6. DOMFocusOut

  7. DOMActivate

5. XForms Model Bindings

There are two type of bindings in the XForms document:

  1. Bindings between controls and document instance nodes

    Currently, we've seen that this type of binding is limited in that it can bind controls only to instance elements and not to attributes

  2. Bindings between document instance nodes and XSD elements or types

    This binding is also limited to the instance element. The element attributes are ignored for now.

    An instance node type can be declared explicitly by using the type attribute of a binding element or by using the ID attribute of the corresponding bind element (if any) to lookup the element from the XSD document having the same ID.

    Also, the binding may be implicitly by searching the XSD element corresponding to the instance element (i.e. the element that has the same XPath path as the instance element)

Thus, resolving the instance element type is done as follow:

  1. Use the type specified for the instance element by a bind declaration

  2. If no type was specified use the ID of the corresponding bind declaration if any, and search the element in the XML Schema document having the same ID.

  3. If not type nor ID was specified using a bind declaration then search in the schema for the element corresponding to the instance element (this is the element having the same XPath path as the instance element)

6. XML Schema Support

The XML Schema engine is interpreting only simple types. The most important limitation is that attributes cannot be typed or constrained - this is expected to change in the future.

Here is the complete list of element supported by the XML Schema engine. Any other element is ignored.

6.1. Supported elements

Element

Attributes

schema

targetNamespace

element

ref, id, name, type, fixed, default

simpleType

name

complexType

ignored

restriction

base

enumeration

value

pattern

value

minInclusive

value

minExclusive

value

maxInclusive

value

maxExclusive

value

length

value

minLength

value

maxLength

value

totalDigits

value

fractionDigits

value

whiteSpace

value

6.2. Supported data types

Type Name

Java Object

anyType

String

anyURI

java.net.URI

boolean

Boolean

integer

Integer

decimal

java.math.BigDecimal

double

Double

float

Float

int

Integer

long

Long

short

Integer

byte

Integer

string

String

token

String

normalizedString

String

time

java.util.Date

date

java.util.Date

dateTime

java.util.Date

7. SWT Mapping

The SWT form builder is mapping XForms controls to SWT controls as follow.

XForms Control

SWT Control

group

Composite

group having a label element

Group

input

Text

secret

Text having SWT.PASSWORD style

textarea

Text having SWT.MULTI style

label

Label

hint

tootltip text of the container control

select (appearance=compact) – the default

List

select (appearance=minimal)

List

select (appearance=full)

check boxes group (Group + Buttons having SWT.CHECK style)

select1 (appearance=compact) – the default

List

select1 (appearance=minimal)

List

select1 (appearance=full)

check boxes group (Group + Buttons having SWT.CHECK style)

trigger

Button

submit

Button

output

Label

xhtml:body

ScrolledForm

Any of these default mapping can be changed using style sheet. To change the SWT control you may use the “control” style attribtue.

8. SWT Style Sheets

Here is the complete list of style attributes supported by the SWT form builder.

Style attribute

Values

Description

control

label | composite | group | text | textArea | password | styledText | button | check | radio | list | combo | form | scrolledForm | section

The SWT control to be used to display the XForms control

bgcolor

#RGB, 0xRGB, RGB where RGB is the HEX representation of the color components

The background color

color

#RGB, 0xRGB, RGB where RGB is the HEX representation of the color components

The foreground color

font

A font string identifier as recognized by SWT ** this is subject to change **

The control font

font-size

integer

the font size

font-style

bold | italic | normal

the font style

border

true | false

SWT.BORDER

wrap

true | false

SWT.WRAP

readOnly

true | false

SWT.READ_ONLY

multi

true | false

SWT.MULTI

flat

true | false

SWT.FLAT

hscroll

true | false

SWT.H_SCROLL

vscroll

true | false

SWT.V_SCROLL

shadow

true | false

SWT.SHADOW

separator

true | false

SWT.SEPARATOR

layout

grid | table | fill

The default layout for Composite is FillLayout, for Group is FillLayout using SWT.VERTICAL style, for Form, ScrolledForm and Section is TableWrapLayout

layout-cols

integer

the number of columns. applies only for table and grid layouts

layout-hspacing

integer

the horizontal spacing used between controls

layout-vspacing

integer

the vertical spacing used between controls

layout-equalWidth

true | false

whether or not the columns should be equals in width (applies for table and grid)

layout-bottom

integer

spacing to be used on bottom margin

layout-left

integer

spacing to be used on left margin

layout-right

integer

spacing to be used on right margin

layout-top

integer

spacing to be used on top margin

layout-padding

integer

spacing to be used on all 4 margins

layout-spacing

integer

spacing to be used between controls

layout-type

vertical | horizontal

the layout type applies only for “fill” layout

align

left | center | right | fill

horizontal alignment

valign

top | center | bottom | fill

vertical alignment

hgrab

true | false

grab excess horizontal space

vgrab

true | false

grab excess vertical space

colspan

integer

column span

rowspan

integer

row span

height

integer

the control height. This is a hint and the final height depends on the layout

width

integer

the control width. This is a hint and the final height depends on the layout

form-toolkit-style

true | false

whether or not the control should be adapted to the current form toolkit

section-toggle

twistie | tree

the style of the Section toggle

section-expand

true | false

whether or not the section should be expanded by default

section-description

true | false

whether the section has a description, in this case the “hint” element will be used as the description

9. Using the API

Warning: The API is still under development so it may change in the future.

The XForms core “org.nuxeo.xforms.core” is structured as follow:

  1. XML Parser module: implement a generic XML parser used by other modules to load XML files

  2. XForms engine: implements an XForms parser, defines the XForms model and provides the XFormsProcessor class as a facade to XForms core

  3. XSD engine: implements a XSD parser and defines the XSD model

  4. CSS engine: builds StyleSheet objects from CSS files

  5. UI layer: defines an interfaces and abstract classes for XForms UI implementors

The UI XForms module provides generic functionality and configuration through the XFormsProcessor class that is the main entry point to the XForms core.

To begin a new XForms processing session you need to instantiated a XFormsProcessor and optionally configure it by adding custom event handlers. Then you may load an XForms from an input source like a file, URL, in-memory string or an opened input stream using the XInputSource abstraction. Example:

XFormsProcessor proc = new XformsProcessor();
XInputSource src = new FileInputSource(new File("example1.xhtml"));
XForm form = proc.load(src);

The loadForm() method is returning the loaded “XForm” object on success. The “XForm” object is the root object in the XForms object model. Using it you can query or modify the XForms tree. You can also retrieve the currently loaded “Xform” object later by calling ”XinputSource.getForm()” method. When loading another “XForm” using the same processor the current “XForm” object is disposed if any.

After a XForm was loaded you can build a UIForm object using an existing UIBuilder implementation.

The “org.nuxeo.xforms.ui” package is providing a SWT implementation of the UIBuilder: “SWTBuilder”. You can build a SWT form with UIForm uiForm = new SWTBuilder().build(form, parent); where “parent” is the SWT Composite control where that will host the form. You can now access the widgets build to render the XForms controls to customize them if needed or to add listeners for events not handled by the XForms engine.

There are two ways to do this.:

  1. You can get the SWT control by using the IDs attribute specified on the controls in the XForms file:

    // get the UI control having the ID “title”UIControl control = uiForm.getControl(“testButton”);

  2. You can find the control corresponding to a document instance node by using the XPath path corresponding to the instance node that is bound to the control:

    // get the UI control used to edit the instance element having the path// “/document/metatada/title” in the default modelUIControl control = uiForm.findControl(“/document/metatada/title”);

Using these methods we can write the following code that is adding a selection listener to a button so that when the button is clicked a message dialog is opened to show the current value of the control used to edit the /document/metadata/title instance element:

UIControl control = uiForm.getControl("testButton");
if (control != null) {
    final Button button = (Button)control.getControl();
    button.addSelectionListener(new SelectionAdapter() {
        public void widgetSelected(SelectionEvent event) {
            UIControl title = uiForm.findControl("/document/metatada/title");
            if (title != null) {
                MessageDialog.openInformation(button.getShell(), "Example 1", "The title value is: "+title.getXMLValue());
            } else {
                MessageDialog.openError(button.getShell(), "Example 1", "Cannot find control bound to \"/document/metatada/title\"");
            }
        }
    });
}

Here is the complete example code:

public class Example1 {

 public static void main(String[] args) {
 Display display = null;
 try {
 
 display = new Display();
 final Shell shell = new Shell(display);
 shell.setText("SWT XForms - Example 1");
 shell.setLayout( new FillLayout());

 XFormsProcessor proc = new XformsProcessor();
 XInputSource src = new URLInputSource (Example1.class.getResource("example1.xhtml"));
 XForm form = proc.load(src);
            
            final UIForm uiForm = new SWTBuilder().build(form, shell);

            UIControl control = uiForm.getControl("testButton");
            if (control != null) {
                final Button button = (Button)control.getControl();
                button.addSelectionListener(new SelectionAdapter() {
                    public void widgetSelected(SelectionEvent event) {
                        UIControl title = uiForm.findControl("/document/title");
                        if (title != null) {
                            MessageDialog.openInformation(button.getShell(), "Example 1","The title value is: "+title.getXMLValue());
                        } else {
                            MessageDialog.openError(button.getShell(), "Example 1", "Cannot find control bound to \"/document/title\"");
                        }
                    }
                });
            }
            shell.setBounds(10, 10, 400, 200);
            shell.open();
            while( !shell.isDisposed()) {
                if(!display.readAndDispatch()) 
                    display.sleep();
            }
    
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (display != null) display.dispose();
        }
        
    }
    
}

This example may be run using the following XForms file example1.xhtml:

<xhtml:html xmlns="http://www.w3.org/2002/xforms" xmlns:xf="http://www.w3.org/2002/xforms"
 xmlns:xhtml="http://www.w3.org/1999/xhtml">
    <model>
        <instance>
            <document>
                <title>the default title</title>
            </document>
        </instance>
    </model>
    <xhtml:body style="layout: grid; layout-padding: 10; layout-spacing: 10; layout-cols: 3">
        <label>Example 1</label>
        <input ref="/document/title" style="border:true; align: fill; hgrab: true">
            <label>The title</label>
            <hint>The default value is:</hint>
        </input>
        <trigger id="testButton" style="form-toolkit-style: false;">
            <label>Click here</label>
        </trigger>
    </xhtml:body>
</xhtml:html>

You can find the example sources in plugin org.nuxeo.xforms.ui: org.nuxeo.xforms.ui.test.Example1.java

To run the example right click on the file in Eclipse Java perspective and choose Run SWT application. Here is the generated dialog:

When you click on the button the current value of the title field will be displayed

A more complex example can be found in the org.nuxeo.xforms.ui.editors plug-in. The FormViewer class is a generic form viewer that encapsulate a XFormsProcessor and display the current view loaded by the processor. The viewer also implements the auto-validation using XForms core API.

10. APPENDIX

10.1. XForms Example

There is an example of an XForms document described in three fiels:

  1. an XML file containing the XForms definition

  2. a XSD file containing the model schema

  3. a CSS file to be used to generate a SWT UI form

These example files can be found in org.nuxeo.xforms.ui plug-in under the org.nuxeo.xforms.ui.test package: document.xhtml, document.xsd, document.css

10.2. XForms File

        <xhtml:html xmlns="
        
          http://www.w3.org/2002/xforms
        
        " xmlns:xsd="
        
          http://www.w3.org/2001/XMLSchema
        
        " xmlns:xf="
        
          http://www.w3.org/2002/xforms
        
        " xmlns:xhtml="
        
          http://www.w3.org/1999/xhtml
        
        " xmlns:ev="
        
          http://www.w3.org/2001/xml-events
        
        "><xhtml:link rel="StyleSheet" type="text/css" href="document.css"/><model schema="document.xsd"><instance> <document> <metadata> <title>My default title</title><mdate/><author>me</author><description>This is the default description</description><language>fr</language><ctype>plain</ctype></metadata> <content/> </document> </instance> <submission id="submit1" action="stdout" method="POST" indent="true"/> <bind nodeset="/document/metadata/title" id="title" required="true()"/> <bind nodeset="/document/metadata/description" id="description"/></model><xhtml:body id="document" ref="/document"><label>My Test Document</label><group id="metadata" ref="/document/metadata"><label>Document Metadata</label> <input class="field" bind="title"><label class="fieldLabel">Title</label><hint>The document title</hint></input> <input class="field" ref="mdate"><label class="fieldLabel">Modification Date</label><hint>The last modification date of the document</hint></input><input class="field" ref="author"> <label class="fieldLabel">Author</label> <hint>The document creator</hint> </input><textarea class="field" id="description" ref="description"> <label class="fieldLabel">Description</label> <hint>The document description</hint> </textarea> <select1 class="field" appearance="minimal" ref="language"><label class="fieldLabel">Language</label><hint>The document language</hint> <item> <label>English</label> <value>en</value></item><item><label>French</label><value>fr</value></item><item> <label>Romanian</label> <value>ro</value></item> </select1> <select1 class="groupField" appearance="full" ref="ctype"><label>Content Type</label> <item><label>Plain Text</label><value>plain</value></item><item> <label>HTML Text</label> <value>html</value></item></select1></group> <group id="contentGroup"><label>Document Content</label><output class="onecol" ref="metadata/title"/><output class="onecol">Type here anything you want ...</output> <textarea id="content" ref="/document/content"/> <output style="separator: horizontal; align: fill"/></group><group id="buttonBar"> <trigger> <label>Reset</label> <reset ev:event="DOMActivate"/> </trigger> <trigger> <label>Apply</label> <revalidate ev:event="DOMActivate"/> </trigger> <submit submission="submit1"> <label>Submit</label></submit></group></xhtml:body></xhtml:html>
      

10.3. XMLSchema File

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns="http://www.w3.org/2001/XMLSchema"
                 xmlns:xs="http://www.w3.org/2001/XMLSchema"
                 xmlns:myxs="http://www.w3.org/2001/MyXMLSchema"
                 targetNamespace="http://www.w3.org/2001/MyXMLSchema">

         <xs:element name="document">
                 <xs:complexType>
                         <xs:sequence>
                                 <xs:element ref="metadata"/>
                                 <xs:element id="content" type="xs:string"/>
                         </xs:sequence>
                 </xs:complexType>
         </xs:element>

         <xs:element name="metadata">
                 <xs:complexType>
                         <xs:sequence>
                                 <xs:element name="title" id="title" type="myxs:titleString"/>
                                 <xs:element name="author" id="author" type="myxs:authorString"/>
                                 <xs:element name="mdate" id="mdate" type="xs:string"/>
                                 <xs:element name="description" id="description" type="xs:string"/>
                                 <xs:element name="language" id="language" type="myxs:language"/>
                                 <xs:element name="ctype" id="ctype" type="xs:string"/>
                         </xs:sequence>
                 </xs:complexType>
         </xs:element>

         <xs:simpleType id="titleString" name="titleString">
                 <xs:restriction base="xs:string">
                         <xs:maxLength value="64"/>
                         <xs:minLength value="8"/>
                 </xs:restriction>
         </xs:simpleType>

         <xs:simpleType id="authorString" name="authorString">
                 <xs:restriction base="xs:string">
                         <xs:pattern value=".*@.*"/>
                 </xs:restriction>
         </xs:simpleType>

         <xs:simpleType id="language" name="language">
                 <xs:restriction base="xs:string">
                         <xs:enumeration value="en"/>
                         <xs:enumeration value="fr"/>
                         <xs:enumeration value="ro"/>
                 </xs:restriction>
         </xs:simpleType>

</xs:schema>

10.4. CSS file

#document {
 layout: table;
 layout-padding: 10;
}

#metadata {
 control: section;
 section-description: false;
 section-toggle: twistie;
 section-expand: true;
 layout: table;
 layout-cols: 2;
 hgrab: true;
 align: fill;
}

#contentGroup {
 control: section;
 section-description: false;
 section-toggle: twistie;
 section-expand: true;
 layout: table;
 layout-cols: 1;
 vgrab: true;
 hgrab: true;
 align: fill;
}

#buttonBar {
 layout: fill;
 align: right;
 layout-spacing: 10;

}

#description {
 border: true;
 height: 100;
 vscroll: true;
 hscroll: true;
 align: fill;
 hgrab: true;
}

#content {
 border: true;
 vscroll: true;
 hscroll: true;
 wrap: true;
 align: fill;
 vgrab: true;
 valign: fill;
 hgrab: true;
 height: 200;
}

.field {
 border: true;
 align: fill;
 hgrab: true;
}

.fieldLabel {
 align: left;
 color: #0000ff;

}

.groupField {
 border: true;
 align: fill;
 hgrab: true;
 colspan: 2;
 layout: fill;
 layout-type: vertical;
 layout-spacing: 5;
 layout-padding: 5;
 color: #0000ff;
}
This site is powered by CPS, which includes CPSSkins. CPS and its design are Copyright © 2002-2006 Nuxeo SAS. CPSSkins is Copyright © 2003-2006 Jean-Marc Orliaguet.
vPOWEREDBYCPS2.png
valid_xhtml.png
valid_css.png