Task 182: .EP File Format
Task 182: .EP File Format
File Format Specifications for .EP
The .EP file format is used by Pencil Project (now known as Evolus Pencil), an open-source GUI prototyping and diagramming tool. It is an XML-based format for storing document data, including pages, shapes, and their properties. The format is not formally standardized but is defined by the application's implementation. The file is plain text (ASCII/UTF-8) and can be edited with any text editor, though it's intended for use with the Pencil application. The structure is as follows:
- File header: Starts with an XML declaration, e.g.,
<?xml version="1.0" encoding="UTF-8"?>
. - Root element:
<Document xmlns="http://www.evolus.vn/Namespace/Pencil">
. - Optional
<Properties>
element for document-level properties (often empty). <Pages>
element containing one or more<Page>
elements.- Each
<Page>
has: - Attributes like
id
. <Properties>
with child<Property>
elements for page-specific settings (name, type, value).<Content>
containing shapes (<Shape>
elements with their own properties and definitions).- Namespaces include
xmlns:p="http://www.evolus.vn/Namespace/Pencil"
for referencing stencil definitions.
The format allows for dynamic properties via <Property>
elements, which have name
and type
attributes, and text content as the value.
- List of all the properties of this file format intrinsic to its file system:
- XML Version (from the declaration, e.g., "1.0")
- Encoding (from the declaration, e.g., "UTF-8")
- Namespace (the main namespace URI, "http://www.evolus.vn/Namespace/Pencil")
- Document Properties (key-value pairs from document-level
<Properties>
, if present; e.g., custom metadata) - Number of Pages (count of
<Page>
elements) - Page ID (per page, from
id
attribute) - Page Name (per page, from
<Property name="name" type="String">
) - Page Width (per page, from
<Property name="width" type="Dimension">
) - Page Height (per page, from
<Property name="height" type="Dimension">
) - Page Background Color (per page, from
<Property name="backgroundColor" type="Color">
) - Page Scale (per page, from
<Property name="scale" type="Real">
) - Page Note (per page, from
<Property name="note" type="PlainText">
)
These are the core structural properties derived from the XML schema used in the format. Shape-specific properties (e.g., fillColor, box) are not listed as "intrinsic" here, as they are variable and content-dependent.
Two direct download links for files of format .EP:
- https://git.andros.dev/andros/flask-note/raw/commit/4a44c1b9e959798f3309925e7bb3cd42a1b365a5/sketching/sketching.ep
- https://gitlab.agetic.gob.bo/gcallejas/ModuloPersonal/-/raw/v0.2.2/doc/mockups/Mockups_Proyecto_fuente.ep
Ghost blog embedded HTML JavaScript for drag and drop .EP file dump:
- Python class for .EP file handling:
import xml.etree.ElementTree as ET
class EPFileHandler:
def __init__(self, filepath):
self.filepath = filepath
self.tree = None
self.root = None
def open(self):
self.tree = ET.parse(self.filepath)
self.root = self.tree.getroot()
def read_properties(self):
if not self.root:
raise ValueError("File not opened")
properties = {
'xml_version': self.tree.docinfo.xml_version or '1.0',
'encoding': self.tree.docinfo.encoding or 'UTF-8',
'namespace': self.root.attrib.get('xmlns', 'http://www.evolus.vn/Namespace/Pencil'),
'document_properties': {},
'number_of_pages': 0,
'pages': []
}
doc_props = self.root.find('Properties')
if doc_props is not None:
for prop in doc_props.findall('Property'):
name = prop.attrib.get('name')
if name:
properties['document_properties'][name] = prop.text or ''
pages = self.root.findall('.//Page')
properties['number_of_pages'] = len(pages)
for page in pages:
page_info = {
'id': page.attrib.get('id', 'N/A'),
'name': 'N/A',
'width': 'N/A',
'height': 'N/A',
'backgroundColor': 'N/A',
'scale': 'N/A',
'note': 'N/A'
}
page_props = page.find('Properties')
if page_props is not None:
for prop in page_props.findall('Property'):
name = prop.attrib.get('name')
if name in page_info:
page_info[name] = prop.text or 'N/A'
properties['pages'].append(page_info)
return properties
def print_properties(self):
props = self.read_properties()
print("Properties of .EP file:")
print(f"XML Version: {props['xml_version']}")
print(f"Encoding: {props['encoding']}")
print(f"Namespace: {props['namespace']}")
print("Document Properties:")
for key, val in props['document_properties'].items():
print(f" - {key}: {val}")
if not props['document_properties']:
print(" None")
print(f"Number of Pages: {props['number_of_pages']}")
for i, page in enumerate(props['pages'], 1):
print(f"\nPage {i}:")
print(f" ID: {page['id']}")
print(f" Name: {page['name']}")
print(f" Width: {page['width']}")
print(f" Height: {page['height']}")
print(f" Background Color: {page['backgroundColor']}")
print(f" Scale: {page['scale']}")
print(f" Note: {page['note']}")
def write(self, new_properties):
# Basic write: Update page names as example; extend as needed
if not self.root:
raise ValueError("File not opened")
pages = self.root.findall('.//Page')
if 'pages' in new_properties and len(new_properties['pages']) == len(pages):
for page, new_page in zip(pages, new_properties['pages']):
page_props = page.find('Properties')
if page_props is not None:
name_prop = page_props.find("Property[@name='name']")
if name_prop is not None:
name_prop.text = new_page.get('name', name_prop.text)
self.tree.write(self.filepath, encoding='utf-8', xml_declaration=True)
# Example usage:
# handler = EPFileHandler('example.ep')
# handler.open()
# handler.print_properties()
# handler.write({'pages': [{'name': 'New Name'}]})
- Java class for .EP file handling:
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import java.io.File;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class EPFileHandler {
private String filepath;
private Document doc;
public EPFileHandler(String filepath) {
this.filepath = filepath;
}
public void open() throws Exception {
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
this.doc = dBuilder.parse(new File(filepath));
this.doc.getDocumentElement().normalize();
}
private Map<String, Object> readProperties() {
Map<String, Object> properties = new HashMap<>();
properties.put("xml_version", "1.0"); // Fixed, as Java doesn't expose easily
properties.put("encoding", "UTF-8"); // Fixed
properties.put("namespace", doc.getDocumentElement().getAttribute("xmlns"));
Map<String, String> docProps = new HashMap<>();
Element docPropertiesEl = (Element) doc.getElementsByTagName("Properties").item(0);
if (docPropertiesEl != null) {
NodeList propList = docPropertiesEl.getElementsByTagName("Property");
for (int i = 0; i < propList.getLength(); i++) {
Element prop = (Element) propList.item(i);
String name = prop.getAttribute("name");
if (!name.isEmpty()) {
docProps.put(name, prop.getTextContent());
}
}
}
properties.put("document_properties", docProps);
NodeList pages = doc.getElementsByTagName("Page");
properties.put("number_of_pages", pages.getLength());
List<Map<String, String>> pageList = new ArrayList<>();
for (int i = 0; i < pages.getLength(); i++) {
Element page = (Element) pages.item(i);
Map<String, String> pageInfo = new HashMap<>();
pageInfo.put("id", page.getAttribute("id"));
pageInfo.put("name", "N/A");
pageInfo.put("width", "N/A");
pageInfo.put("height", "N/A");
pageInfo.put("backgroundColor", "N/A");
pageInfo.put("scale", "N/A");
pageInfo.put("note", "N/A");
Element pagePropsEl = (Element) page.getElementsByTagName("Properties").item(0);
if (pagePropsEl != null) {
NodeList propList = pagePropsEl.getElementsByTagName("Property");
for (int j = 0; j < propList.getLength(); j++) {
Element prop = (Element) propList.item(j);
String name = prop.getAttribute("name");
if (pageInfo.containsKey(name)) {
pageInfo.put(name, prop.getTextContent());
}
}
}
pageList.add(pageInfo);
}
properties.put("pages", pageList);
return properties;
}
public void printProperties() {
Map<String, Object> props = readProperties();
System.out.println("Properties of .EP file:");
System.out.println("XML Version: " + props.get("xml_version"));
System.out.println("Encoding: " + props.get("encoding"));
System.out.println("Namespace: " + props.get("namespace"));
System.out.println("Document Properties:");
Map<String, String> docProps = (Map<String, String>) props.get("document_properties");
if (docProps.isEmpty()) {
System.out.println(" None");
} else {
for (Map.Entry<String, String> entry : docProps.entrySet()) {
System.out.println(" - " + entry.getKey() + ": " + entry.getValue());
}
}
System.out.println("Number of Pages: " + props.get("number_of_pages"));
List<Map<String, String>> pages = (List<Map<String, String>>) props.get("pages");
for (int i = 0; i < pages.size(); i++) {
Map<String, String> page = pages.get(i);
System.out.println("\nPage " + (i + 1) + ":");
System.out.println(" ID: " + page.get("id"));
System.out.println(" Name: " + page.get("name"));
System.out.println(" Width: " + page.get("width"));
System.out.println(" Height: " + page.get("height"));
System.out.println(" Background Color: " + page.get("backgroundColor"));
System.out.println(" Scale: " + page.get("scale"));
System.out.println(" Note: " + page.get("note"));
}
}
public void write(Map<String, Object> newProperties) throws Exception {
// Basic write: Update page names as example
NodeList pages = doc.getElementsByTagName("Page");
List<Map<String, String>> newPages = (List<Map<String, String>>) newProperties.get("pages");
if (newPages != null && newPages.size() == pages.getLength()) {
for (int i = 0; i < pages.getLength(); i++) {
Element page = (Element) pages.item(i);
Element pagePropsEl = (Element) page.getElementsByTagName("Properties").item(0);
if (pagePropsEl != null) {
NodeList propList = pagePropsEl.getElementsByTagName("Property");
for (int j = 0; j < propList.getLength(); j++) {
Element prop = (Element) propList.item(j);
if ("name".equals(prop.getAttribute("name"))) {
prop.setTextContent(newPages.get(i).get("name"));
}
}
}
}
}
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
DOMSource source = new DOMSource(doc);
StreamResult result = new StreamResult(new File(filepath));
transformer.transform(source, result);
}
// Example usage:
// public static void main(String[] args) throws Exception {
// EPFileHandler handler = new EPFileHandler("example.ep");
// handler.open();
// handler.printProperties();
// Map<String, Object> newProps = new HashMap<>();
// List<Map<String, String>> pages = new ArrayList<>();
// Map<String, String> page1 = new HashMap<>();
// page1.put("name", "New Name");
// pages.add(page1);
// newProps.put("pages", pages);
// handler.write(newProps);
// }
}
- JavaScript class for .EP file handling (Node.js, using xml2js for parsing):
const fs = require('fs');
const xml2js = require('xml2js');
class EPFileHandler {
constructor(filepath) {
this.filepath = filepath;
this.xmlData = null;
}
open(callback) {
fs.readFile(this.filepath, 'utf8', (err, data) => {
if (err) return callback(err);
const parser = new xml2js.Parser({ explicitArray: false });
parser.parseString(data, (err, result) => {
if (err) return callback(err);
this.xmlData = result;
callback(null);
});
});
}
readProperties() {
if (!this.xmlData) throw new Error('File not opened');
const root = this.xmlData.Document || {};
const properties = {
xml_version: '1.0', // Assumed
encoding: 'UTF-8', // Assumed
namespace: root['$'] ? root['$'].xmlns : 'http://www.evolus.vn/Namespace/Pencil',
document_properties: {},
number_of_pages: 0,
pages: []
};
if (root.Properties && root.Properties.Property) {
const docProps = Array.isArray(root.Properties.Property) ? root.Properties.Property : [root.Properties.Property];
docProps.forEach(prop => {
const name = prop['$'].name;
if (name) {
properties.document_properties[name] = prop._ || prop['#text'] || '';
}
});
}
let pages = root.Pages ? (Array.isArray(root.Pages.Page) ? root.Pages.Page : [root.Pages.Page]) : [];
properties.number_of_pages = pages.length;
pages.forEach(page => {
const pageInfo = {
id: page['$'] ? page['$'].id : 'N/A',
name: 'N/A',
width: 'N/A',
height: 'N/A',
backgroundColor: 'N/A',
scale: 'N/A',
note: 'N/A'
};
if (page.Properties && page.Properties.Property) {
const pageProps = Array.isArray(page.Properties.Property) ? page.Properties.Property : [page.Properties.Property];
pageProps.forEach(prop => {
const name = prop['$'].name;
if (name in pageInfo) {
pageInfo[name] = prop._ || prop['#text'] || 'N/A';
}
});
}
properties.pages.push(pageInfo);
});
return properties;
}
printProperties() {
const props = this.readProperties();
console.log('Properties of .EP file:');
console.log(`XML Version: ${props.xml_version}`);
console.log(`Encoding: ${props.encoding}`);
console.log(`Namespace: ${props.namespace}`);
console.log('Document Properties:');
const docProps = props.document_properties;
if (Object.keys(docProps).length === 0) {
console.log(' None');
} else {
for (const [key, val] of Object.entries(docProps)) {
console.log(` - ${key}: ${val}`);
}
}
console.log(`Number of Pages: ${props.number_of_pages}`);
props.pages.forEach((page, index) => {
console.log(`\nPage ${index + 1}:`);
console.log(` ID: ${page.id}`);
console.log(` Name: ${page.name}`);
console.log(` Width: ${page.width}`);
console.log(` Height: ${page.height}`);
console.log(` Background Color: ${page.backgroundColor}`);
console.log(` Scale: ${page.scale}`);
console.log(` Note: ${page.note}`);
});
}
write(newProperties, callback) {
// Basic write: Update page names as example
if (!this.xmlData) throw new Error('File not opened');
const pages = this.xmlData.Document.Pages.Page;
const newPages = newProperties.pages || [];
if (Array.isArray(pages) && pages.length === newPages.length) {
pages.forEach((page, i) => {
if (page.Properties && page.Properties.Property) {
let props = Array.isArray(page.Properties.Property) ? page.Properties.Property : [page.Properties.Property];
props = props.map(prop => {
if (prop['$'].name === 'name') {
prop._ = newPages[i].name;
}
return prop;
});
page.Properties.Property = props;
}
});
}
const builder = new xml2js.Builder({ renderOpts: { pretty: true, indent: ' ', newline: '\n' }, xmldec: { version: '1.0', encoding: 'UTF-8' } });
const xml = builder.buildObject(this.xmlData);
fs.writeFile(this.filepath, xml, callback);
}
}
// Example usage:
// const handler = new EPFileHandler('example.ep');
// handler.open((err) => {
// if (err) console.error(err);
// handler.printProperties();
// handler.write({ pages: [{ name: 'New Name' }] }, (err) => {
// if (err) console.error(err);
// });
// });
- C class (using C++ for XML parsing with tinyxml2 library, assume included):
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <map>
#include "tinyxml2.h" // Assume tinyxml2.h is included for XML parsing
using namespace tinyxml2;
class EPFileHandler {
private:
std::string filepath;
XMLDocument doc;
public:
EPFileHandler(const std::string& fp) : filepath(fp) {}
bool open() {
return doc.LoadFile(filepath.c_str()) == XML_SUCCESS;
}
std::map<std::string, std::string> readDocumentProperties(XMLElement* propsEl) {
std::map<std::string, std::string> props;
if (propsEl) {
for (XMLElement* prop = propsEl->FirstChildElement("Property"); prop; prop = prop->NextSiblingElement("Property")) {
const char* name = prop->Attribute("name");
if (name) {
props[name] = prop->GetText() ? prop->GetText() : "";
}
}
}
return props;
}
void printProperties() {
if (doc.Error()) {
std::cerr << "Error loading file" << std::endl;
return;
}
XMLElement* root = doc.RootElement();
if (!root) return;
std::cout << "Properties of .EP file:" << std::endl;
std::cout << "XML Version: 1.0" << std::endl; // Assumed
std::cout << "Encoding: UTF-8" << std::endl; // Assumed
std::cout << "Namespace: " << (root->Attribute("xmlns") ? root->Attribute("xmlns") : "http://www.evolus.vn/Namespace/Pencil") << std::endl;
auto docProps = readDocumentProperties(root->FirstChildElement("Properties"));
std::cout << "Document Properties:" << std::endl;
if (docProps.empty()) {
std::cout << " None" << std::endl;
} else {
for (const auto& kv : docProps) {
std::cout << " - " << kv.first << ": " << kv.second << std::endl;
}
}
int numPages = 0;
std::vector<std::map<std::string, std::string>> pages;
for (XMLElement* page = root->FirstChildElement("Pages")->FirstChildElement("Page"); page; page = page->NextSiblingElement("Page")) {
numPages++;
std::map<std::string, std::string> pageInfo;
pageInfo["id"] = page->Attribute("id") ? page->Attribute("id") : "N/A";
pageInfo["name"] = "N/A";
pageInfo["width"] = "N/A";
pageInfo["height"] = "N/A";
pageInfo["backgroundColor"] = "N/A";
pageInfo["scale"] = "N/A";
pageInfo["note"] = "N/A";
auto pageProps = readDocumentProperties(page->FirstChildElement("Properties"));
for (const auto& kv : pageProps) {
if (pageInfo.count(kv.first)) {
pageInfo[kv.first] = kv.second;
}
}
pages.push_back(pageInfo);
}
std::cout << "Number of Pages: " << numPages << std::endl;
for (size_t i = 0; i < pages.size(); ++i) {
auto& page = pages[i];
std::cout << "\nPage " << (i + 1) << ":" << std::endl;
std::cout << " ID: " << page["id"] << std::endl;
std::cout << " Name: " << page["name"] << std::endl;
std::cout << " Width: " << page["width"] << std::endl;
std::cout << " Height: " << page["height"] << std::endl;
std::cout << " Background Color: " << page["backgroundColor"] << std::endl;
std::cout << " Scale: " << page["scale"] << std::endl;
std::cout << " Note: " << page["note"] << std::endl;
}
}
bool write(const std::vector<std::map<std::string, std::string>>& newPages) {
// Basic write: Update page names
XMLElement* pagesEl = doc.RootElement()->FirstChildElement("Pages");
if (!pagesEl) return false;
int index = 0;
for (XMLElement* page = pagesEl->FirstChildElement("Page"); page; page = page->NextSiblingElement("Page")) {
if (index >= newPages.size()) break;
XMLElement* propsEl = page->FirstChildElement("Properties");
if (propsEl) {
for (XMLElement* prop = propsEl->FirstChildElement("Property"); prop; prop = prop->NextSiblingElement("Property")) {
if (std::string(prop->Attribute("name")) == "name") {
prop->SetText(newPages[index]["name"].c_str());
}
}
}
index++;
}
return doc.SaveFile(filepath.c_str()) == XML_SUCCESS;
}
};
// Example usage:
// int main() {
// EPFileHandler handler("example.ep");
// if (handler.open()) {
// handler.printProperties();
// std::vector<std::map<std::string, std::string>> newPages = {{{"name", "New Name"}}};
// handler.write(newPages);
// }
// return 0;
// }