/* * @(#)ODGStylesReader.java * * Copyright (c) 2007 The authors and contributors of JHotDraw. * You may not use, copy or modify this file, except in compliance with the * accompanying license terms. */ package org.jhotdraw.samples.odg.io; import javax.annotation.Nullable; import java.awt.Color; import java.io.*; import java.util.*; import java.util.HashMap; import net.n3.nanoxml.*; import org.jhotdraw.draw.*; import static org.jhotdraw.samples.odg.ODGConstants.*; import static org.jhotdraw.samples.odg.ODGAttributeKeys.*; /** * ODGStylesReader reads an ODG <document-styles> element, * and creates a map of AttributeKey's and values. * * * @author Werner Randelshofer * @version $Id$ */ public class ODGStylesReader { private static final boolean DEBUG = false; private static class Style extends HashMap<AttributeKey<?>, Object> { private static final long serialVersionUID = 1L; public String name; public String family; public String parentName; } /** * Most office applications support styles within their user interface. * Within this specification, the XML representations of such styles are * referred to as styles. When a differentiation from the other types of * styles is required, they are referred to as common styles. * The term common indicates that this is the type of style that an office * application user considers to be a style. */ private HashMap<String, Style> commonStyles; /** * A master style is a common style that contains formatting information and * additional content that is displayed with the document content when the * style is applied. An example of a master style are master pages. Master * pages can be used in graphical applications. In this case, the additional * content is any drawing shapes that are displayed as the background of the * draw page. Master pages can also be used in text documents. In this case, * the additional content is the headers and footers. Please note that the * content that is contained within master styles is additional content that * influences the representation of a document but does not change the * content of a document. */ private HashMap<String, Style> masterStyles; /** * An automatic style contains formatting properties that, in the user * interface view of a document, are assigned to an object such as a * paragraph. The term automatic indicates that the style is generated * automatically. In other words, formatting properties that are immediately * assigned to a specific object are represented by an automatic style. This * way, a separation of content and layout is achieved. */ private HashMap<String, Style> automaticStyles; /** Creates a new instance. */ public ODGStylesReader() { reset(); } public Map<AttributeKey<?>, Object> getAttributes(String styleName, String familyName) { //String key = familyName+"-"+styleName; String key = styleName; Style style; if (commonStyles.containsKey(key)) { style = commonStyles.get(key); } else if (automaticStyles.containsKey(key)) { style = automaticStyles.get(key); } else if (masterStyles.containsKey(key)) { style = masterStyles.get(key); } else { style = new Style(); } if (style.parentName == null) { return style; } else { HashMap<AttributeKey<?>, Object> a = new HashMap<AttributeKey<?>, Object>(); Map<AttributeKey<?>, Object> parentAttributes = getAttributes(style.parentName, familyName); a.putAll(parentAttributes); a.putAll(style); return a; } } /** * Reads a <document-styles> element from the specified * XML file. * * * @param file A XML file with a <document> root element * or with a <document-styles> root element. */ public void read(File file) throws IOException { BufferedInputStream in = new BufferedInputStream(new FileInputStream(file)); try { read(in); } finally { in.close(); } } /** * Reads a <document-styles> element from the specified * input stream. * * * @param in A input stream with a <document> root element * or with a <document-styles> root element. */ public void read(InputStream in) throws IOException { IXMLParser parser; try { parser = XMLParserFactory.createDefaultXMLParser(); } catch (Exception ex) { InternalError e = new InternalError("Unable to instantiate NanoXML Parser"); e.initCause(ex); throw e; } IXMLReader reader = new StdXMLReader(in); parser.setReader(reader); IXMLElement document; try { document = (IXMLElement) parser.parse(); } catch (XMLException ex) { IOException e = new IOException(ex.getMessage()); e.initCause(ex); throw e; } read(document); } private void reset() { commonStyles = new HashMap<String, Style>(); automaticStyles = new HashMap<String, Style>(); masterStyles = new HashMap<String, Style>(); } /** * Reads a <document-styles> element from the specified * XML element. * * * @param root A <document> element or a * <document-styles> element. */ public void read(IXMLElement root) throws IOException { String name = root.getName(); String ns = root.getNamespace(); if ("document-content".equals(name) && (ns == null || ns.equals(OFFICE_NAMESPACE))) { readDocumentContentElement(root); } else if ("document-styles".equals(name) && (ns == null || ns.equals(OFFICE_NAMESPACE))) { readDocumentStylesElement(root); } else { if (DEBUG) { System.out.println("ODGStylesReader unsupported root element " + root); } } } /** * Reads a <default-style> element from the specified * XML element. * <p> * A default style specifies default formatting properties for a certain * style family. These defaults are used if a formatting property is neither * specified by an automatic nor a common style. Default styles exist for * all style families that are represented by the <style:style> * element specified in section 14.1. * Default styles are represented by the <style:default-style> * element. The only attribute supported by this element is style:family. * Its meaning equals the one of the same attribute for the * <style:style> element, and the same properties child elements are * supported depending on the style family. * * @param elem A <default-style> element. * @param styles Style attributes to be filled in by this method. */ private void readDefaultStyleElement(IXMLElement elem, HashMap<String, Style> styles) throws IOException { String styleName = elem.getAttribute("family", STYLE_NAMESPACE, null); String family = elem.getAttribute("family", STYLE_NAMESPACE, null); String parentStyleName = elem.getAttribute("parent-style-name", STYLE_NAMESPACE, null); if (DEBUG) { System.out.println("ODGStylesReader <default-style family=" + styleName + " ...>...</>"); } if (styleName != null) { Style a = styles.get(styleName); if (a == null) { a = new Style(); a.name = styleName; a.family = family; a.parentName = parentStyleName; styles.put(styleName, a); } for (IXMLElement child : elem.getChildren()) { String ns = child.getNamespace(); String name = child.getName(); if ("drawing-page-properties".equals(name) && (ns == null || ns.equals(STYLE_NAMESPACE))) { readDrawingPagePropertiesElement(child, a); } else if ("graphic-properties".equals(name) && (ns == null || ns.equals(STYLE_NAMESPACE))) { readGraphicPropertiesElement(child, a); } else if ("paragraph-properties".equals(name) && (ns == null || ns.equals(STYLE_NAMESPACE))) { readParagraphPropertiesElement(child, a); } else if ("text-properties".equals(name) && (ns == null || ns.equals(STYLE_NAMESPACE))) { readTextPropertiesElement(child, a); } else { if (DEBUG) { System.out.println("ODGStylesReader unsupported <" + elem.getName() + "> child " + child); } } } } } /** * Reads a <document-content> element from the specified * XML element. * * @param elem A <document-content> element. */ private void readDocumentContentElement(IXMLElement elem) throws IOException { if (DEBUG) { System.out.println("ODGStylesReader <" + elem.getName() + " ...>"); } for (IXMLElement child : elem.getChildren()) { String ns = child.getNamespace(); String name = child.getName(); if ("automatic-styles".equals(name) && (ns == null || ns.equals(OFFICE_NAMESPACE))) { readAutomaticStylesElement(child); } else if ("master-styles".equals(name) && (ns == null || ns.equals(OFFICE_NAMESPACE))) { readStylesElement(child); } else if ("styles".equals(name) && (ns == null || ns.equals(OFFICE_NAMESPACE))) { readStylesElement(child); } } if (DEBUG) { System.out.println("ODGStylesReader </" + elem.getName() + ">"); } } /** * Reads a <document-styles> element from the specified * XML element. * <p> * The document-styles element contains all named styles of * a document, along with the automatic styles needed for the named * styles. * * * @param elem A <document-styles> element. */ private void readDocumentStylesElement(IXMLElement elem) throws IOException { if (DEBUG) { System.out.println("ODGStylesReader <" + elem.getName() + " ...>"); } for (IXMLElement child : elem.getChildren()) { String ns = child.getNamespace(); String name = child.getName(); if ("styles".equals(name) && (ns == null || ns.equals(OFFICE_NAMESPACE))) { readStylesElement(child); } else if ("automatic-styles".equals(name) && (ns == null || ns.equals(OFFICE_NAMESPACE))) { readAutomaticStylesElement(child); } else if ("master-styles".equals(name) && (ns == null || ns.equals(OFFICE_NAMESPACE))) { readMasterStylesElement(child); } else { if (DEBUG) { System.out.println("ODGStylesReader unsupported <" + elem.getName() + "> child " + child); } } } if (DEBUG) { System.out.println("ODGStylesReader </" + elem.getName() + ">"); } } /** * Reads a <style:drawing-page-properties> element from the specified * XML element. * <p> * * @param elem A <style:drawing-page-properties> element. */ private void readDrawingPagePropertiesElement(IXMLElement elem, HashMap<AttributeKey<?>, Object> a) throws IOException { if (DEBUG) { System.out.println("ODGStylesReader unsupported <" + elem.getName() + "> element."); } } /** * Reads a <style:graphic-properties> element from the specified * XML element. * <p> * * @param elem A <style:graphic-properties> element. */ private void readGraphicPropertiesElement(IXMLElement elem, HashMap<AttributeKey<?>, Object> a) throws IOException { // The attribute draw:stroke specifies the style of the stroke on the current object. The value // none means that no stroke is drawn, and the value solid means that a solid stroke is drawn. If // the value is dash, the stroke referenced by the draw:stroke-dash property is drawn. if (elem.hasAttribute("stroke", DRAWING_NAMESPACE)) { STROKE_STYLE.put(a, elem.getAttribute("stroke", DRAWING_NAMESPACE, STROKE_STYLES, null)); } // The attribute svg:stroke-width specifies the width of the stroke on // the current object. if (elem.hasAttribute("stroke-width", SVG_NAMESPACE)) { STROKE_WIDTH.put(a, toLength(elem.getAttribute("stroke-width", SVG_NAMESPACE, null))); } // The attribute svg:stroke-color specifies the color of the stroke on // the current object. if (elem.hasAttribute("stroke-color", SVG_NAMESPACE)) { STROKE_COLOR.put(a, toColor(elem.getAttribute("stroke-color", SVG_NAMESPACE, null))); } // FIXME read draw:marker-start-width, draw:marker-start-center, draw:marker-end-width, // draw:marker-end-centre // The attribute draw:fill specifies the fill style for a graphic // object. Graphic objects that are not closed, such as a path without a // closepath at the end, will not be filled. The fill operation does not // automatically close all open subpaths by connecting the last point of // the subpath with the first point of the subpath before painting the // fill. The attribute has the following values: // • none: the drawing object is not filled. // • solid: the drawing object is filled with color specified by the // draw:fill-color attribute. // • bitmap: the drawing object is filled with the bitmap specified // by the draw:fill-image-name attribute. // • gradient: the drawing object is filled with the gradient specified // by the draw:fill-gradient-name attribute. // • hatch: the drawing object is filled with the hatch specified by // the draw:fill-hatch-name attribute. if (elem.hasAttribute("fill", DRAWING_NAMESPACE)) { FILL_STYLE.put(a, elem.getAttribute("fill", DRAWING_NAMESPACE, FILL_STYLES, null)); } // The attribute draw:fill-color specifies the color of the fill for a // graphic object. It is used only if the draw:fill attribute has the // value solid. if (elem.hasAttribute("fill-color", DRAWING_NAMESPACE)) { FILL_COLOR.put(a, toColor(elem.getAttribute("fill-color", DRAWING_NAMESPACE, null))); } // FIXME read fo:padding-top, fo:padding-bottom, fo:padding-left, // fo:padding-right // FIXME read draw:shadow, draw:shadow-offset-x, draw:shadow-offset-y, // draw:shadow-color for (IXMLElement child : elem.getChildren()) { String ns = child.getNamespace(); String name = child.getName(); // if (DEBUG) System.out.println("ODGStylesReader unsupported <"+elem.getName()+"> child <"+child.getName()+" ...>...</>"); } } /** * Reads a <styles> element from the specified * XML element. * <p> * The <style:style> element can represent paragraph, text, and * graphic styles. * * * @param elem A <style> element. * @param styles Style attributes to be filled in by this method. */ private void readStyleElement(IXMLElement elem, HashMap<String, Style> styles) throws IOException { // The style:name attribute identifies the name of the style. This attribute, combined with the // style:family attribute, uniquely identifies a style. The <office:styles>, // <office:automatic-styles> and <office:master-styles> elements each must not // contain two styles with the same family and the same name. // For automatic styles, a name is generated during document export. If the document is exported // several times, it cannot be assumed that the same name is generated each time. // In an XML document, the name of each style is a unique name that may be independent of the // language selected for an office applications user interface. Usually these names are the ones used // for the English version of the user interface. String styleName = elem.getAttribute("name", STYLE_NAMESPACE, null); String family = elem.getAttribute("family", STYLE_NAMESPACE, null); String parentStyleName = elem.getAttribute("parent-style-name", STYLE_NAMESPACE, null); if (DEBUG) { System.out.println("ODGStylesReader <style name=" + styleName + " ...>...</>"); } if (styleName != null) { Style a = styles.get(styleName); if (a == null) { a = new Style(); a.name = styleName; a.family = family; a.parentName = parentStyleName; styles.put(styleName, a); } for (IXMLElement child : elem.getChildren()) { String ns = child.getNamespace(); String name = child.getName(); if ("drawing-page-properties".equals(name) && (ns == null || ns.equals(STYLE_NAMESPACE))) { readDrawingPagePropertiesElement(child, a); } else if ("graphic-properties".equals(name) && (ns == null || ns.equals(STYLE_NAMESPACE))) { readGraphicPropertiesElement(child, a); } else if ("paragraph-properties".equals(name) && (ns == null || ns.equals(STYLE_NAMESPACE))) { readParagraphPropertiesElement(child, a); } else if ("text-properties".equals(name) && (ns == null || ns.equals(STYLE_NAMESPACE))) { readTextPropertiesElement(child, a); } else { if (DEBUG) { System.out.println("ODGStylesReader unsupported <" + elem.getName() + "> child " + child); } } } } } /** * Reads a <styles> element from the specified * XML element. * <p> * The styles element contains common styles. * * * @param elem A <styles> element. */ private void readStylesElement(IXMLElement elem) throws IOException { readStylesChildren(elem, commonStyles); } /** * Reads the children of a styles element. * * * @param elem A <styles>, <automatic-styles>, * <document-styles> or a <master-styles> element. * @param styles Styles to be filled in by this method. */ private void readStylesChildren(IXMLElement elem, HashMap<String, Style> styles) throws IOException { for (IXMLElement child : elem.getChildren()) { String ns = child.getNamespace(); String name = child.getName(); if ("default-style".equals(name) && (ns == null || ns.equals(STYLE_NAMESPACE))) { readDefaultStyleElement(child, styles); } else if ("layer-set".equals(name) && (ns == null || ns.equals(DRAWING_NAMESPACE))) { readLayerSetElement(child, styles); } else if ("list-style".equals(name) && (ns == null || ns.equals(TEXT_NAMESPACE))) { readListStyleElement(child, styles); } else if ("marker".equals(name) && (ns == null || ns.equals(DRAWING_NAMESPACE))) { readMarkerElement(child, styles); } else if ("master-page".equals(name) && (ns == null || ns.equals(STYLE_NAMESPACE))) { readMasterPageElement(child, styles); } else if ("page-layout".equals(name) && (ns == null || ns.equals(STYLE_NAMESPACE))) { readPageLayoutElement(child, styles); //} else if ("paragraph-properties".equals(name) && (ns == null || ns.equals(STYLE_NAMESPACE))) { // readParagraphPropertiesElement(child, styles); } else if ("style".equals(name) && (ns == null || ns.equals(STYLE_NAMESPACE))) { readStyleElement(child, styles); //} else if ("text-properties".equals(name) && (ns == null || ns.equals(STYLE_NAMESPACE))) { // readTextPropertiesElement(child, styles); } else { if (DEBUG) { System.out.println("ODGStylesReader unsupported <" + elem.getName() + "> child: " + child); } } } } /** * Reads a <automatic-styles> element from the specified * XML element. * <p> * The automatic-styles element contains automatic styles. * * * @param elem A <automatic-styles> element. */ private void readAutomaticStylesElement(IXMLElement elem) throws IOException { readStylesChildren(elem, automaticStyles); } /** * Reads a <draw:layer-put> element from the specified * XML element. * <p> * * @param elem A <layer-put> element. * @param styles Style attributes to be filled in by this method. */ private void readLayerSetElement(IXMLElement elem, HashMap<String, Style> styles) throws IOException { if (DEBUG) { System.out.println("ODGStylesReader unsupported <" + elem.getName() + "> element."); } } /** * Reads a <text:list-style> element from the specified * XML element. * <p> * * @param elem A <list-style> element. * @param styles Style attributes to be filled in by this method. */ private void readListStyleElement(IXMLElement elem, HashMap<String, Style> styles) throws IOException { if (DEBUG) { System.out.println("ODGStylesReader unsupported <" + elem.getName() + "> element."); } } /** * Reads a <master-styles> element from the specified * XML element. * <p> * The master-styles element contains master styles. * * * @param elem A <master-styles> element. */ private void readMasterStylesElement(IXMLElement elem) throws IOException { readStylesChildren(elem, masterStyles); } /** * Reads a <draw:marker> element from the specified * XML element. * <p> * The element <draw:marker> represents a marker, which is used * to draw polygons at the start and end points of strokes. Markers * are not available as automatic styles. * * * @param elem A <master-styles> element. * @param styles Style attributes to be filled in by this method. */ private void readMarkerElement(IXMLElement elem, HashMap<String, Style> styles) throws IOException { //if (DEBUG) System.out.println("ODGStylesReader unsupported <"+elem.getName()+"> element."); } /** * Reads a <style:master-page> element from the specified * XML element. * <p> * * @param elem A <page-layout> element. * @param styles Style attributes to be filled in by this method. */ private void readMasterPageElement(IXMLElement elem, HashMap<String, Style> styles) throws IOException { if (DEBUG) { System.out.println("ODGStylesReader unsupported <" + elem.getName() + "> element."); } } /** * Reads a <style:page-layout> element from the specified * XML element. * <p> * The <style:page-layout> element specifies the physical properties * of a page. This element contains a <style:page-layout-properties> * element which specifies the formatting properties of the page and two * optional elements that specify the properties of headers and footers. * * @param elem A <page-layout> element. * @param styles Style attributes to be filled in by this method. */ private void readPageLayoutElement(IXMLElement elem, HashMap<String, Style> styles) throws IOException { //if (DEBUG) System.out.println("ODGStylesReader unsupported <"+elem.getName()+"> element."); } /** * Reads a <style:paragraph-properties> element from the specified * XML element. * <p> * The properties described in this section can be contained within * paragraph styles (see section 14.8.2), but also within other styles, like * cell styles (see section 14.12.4) They are contained in a * <style:paragraph-properties> element. * * * @param elem A <paragraph-properties> element. * @param a Style attributes to be filled in by this method. */ private void readParagraphPropertiesElement(IXMLElement elem, HashMap<AttributeKey<?>, Object> a) throws IOException { //if (DEBUG) System.out.println("ODGStylesReader unsupported <"+elem.getName()+"> element."); } /** * Reads a <style:text-properties> element from the specified * XML element. * <p> * The properties described in this section can be contained within text * styles (see section 14.8.1), but also within other styles, like paragraph * styles (see section 14.8.2) or cell styles (see section 14.12.4) They are * contained in a <style:text-properties> element. * * * @param elem A <paragraph-properties> element. * @param a Style attributes to be filled in by this method. */ private void readTextPropertiesElement(IXMLElement elem, HashMap<AttributeKey<?>, Object> a) throws IOException { //if (DEBUG) System.out.println("ODGStylesReader unsupported <"+elem.getName()+"> element."); } /** * Returns a value as a length. * * <define name="length"> * <data type="string"> * <param name="pattern">-?([0-9]+(\.[0-9]*)?|\.[0-9]+)((cm)|(mm)|(in)| * (pt)|(pc)|(px))</param> * */ private double toLength(String str) throws IOException { double scaleFactor = 1d; if (str == null || str.length() == 0) { return 0d; } if (str.endsWith("cm")) { str = str.substring(0, str.length() - 2); scaleFactor = 35.43307; } else if (str.endsWith("mm")) { str = str.substring(0, str.length() - 2); scaleFactor = 3.543307; } else if (str.endsWith("in")) { str = str.substring(0, str.length() - 2); scaleFactor = 90; } else if (str.endsWith("pt")) { str = str.substring(0, str.length() - 2); scaleFactor = 1.25; } else if (str.endsWith("pc")) { str = str.substring(0, str.length() - 2); scaleFactor = 15; } else if (str.endsWith("px")) { str = str.substring(0, str.length() - 2); } return Double.parseDouble(str) * scaleFactor; } /** * Reads a color style attribute. * <define name="color"> * <data type="string"> * <param name="pattern">#[0-9a-fA-F]{6}</param> * </data> * </define> */ @Nullable private Color toColor(String value) throws IOException { String str = value; if (str == null) { return null; } if (str.startsWith("#") && str.length() == 7) { return new Color(Integer.decode(str)); } else { return null; } } }