package nota.oxygen.common; import java.awt.Component; import java.awt.Dimension; import java.io.File; import java.io.IOException; import java.io.StringWriter; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.imageio.ImageIO; import javax.imageio.ImageReader; import javax.imageio.stream.FileImageInputStream; import javax.imageio.stream.ImageInputStream; import javax.swing.JTabbedPane; import javax.swing.text.BadLocationException; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.w3c.dom.bootstrap.DOMImplementationRegistry; import org.w3c.dom.ls.DOMImplementationLS; import org.w3c.dom.ls.LSInput; import org.w3c.dom.ls.LSParser; import org.w3c.dom.ls.LSSerializer; import ro.sync.ecss.extensions.api.AuthorAccess; import ro.sync.ecss.extensions.api.AuthorDocumentController; import ro.sync.ecss.extensions.api.AuthorOperationException; import ro.sync.ecss.extensions.api.node.AttrValue; import ro.sync.ecss.extensions.api.node.AuthorDocument; import ro.sync.ecss.extensions.api.node.AuthorDocumentFragment; import ro.sync.ecss.extensions.api.node.AuthorElement; import ro.sync.ecss.extensions.api.node.AuthorNode; import ro.sync.exml.workspace.api.editor.page.author.WSAuthorEditorPageBase; public class Utils { /** * Gets a descendant {@link Element} by id * @param root The root of the descendant tree * @param id The id * @return The first descendant {@link Element} with the given id or {@code null} if no such element exists */ public static Element getDescentantElementById(Element root, String id) { for (int i=0; i<root.getChildNodes().getLength(); i++) { if (root.getChildNodes().item(i) instanceof Element) { Element child = (Element)root.getChildNodes().item(i); if (id.equals(child.getAttribute("id"))) return child; Element tmp = getDescentantElementById(child, id); if (tmp!=null) return tmp; } } return null; } /** * Creates an {@link XPath} * @return The created {@link XPath} */ public static XPath getXPath() { return XPathFactory.newInstance().newXPath(); } /** * Create an {@link XPath} initialized with the given namespace/prefix pair * @param prefix The prefix * @param namespace The namespace * @return The created {@link XPath} */ public static XPath getXPath(String prefix, String namespace) { XPath xpath = getXPath(); xpath.setNamespaceContext(new ManualNamespaceContext(prefix, namespace)); return xpath; } /** * Creates a {@link XPath} inistalized with the given prefix/namespace {@link Map} * @param prefixNSMap The given {@link Map} * @return The created {@link XPath} */ public static XPath getXPath(Map<String,String> prefixNSMap) { XPath xpath = getXPath(); xpath.setNamespaceContext(new ManualNamespaceContext(prefixNSMap)); return xpath; } /** * Gets the nearest ancestor or self {@link AuthorElement} of a given {@link AuthorNode} * @param node The given {@link AuthorNode} * @return The nearest ancestor or self {@link AuthorElement} */ public static AuthorElement getAncestorOrSelfElement(AuthorNode node) { while (node!=null) { if (node instanceof AuthorElement) return (AuthorElement)node; node = node.getParent(); } return null; } public static DOMImplementationLS getDOMImplementationLS() throws AuthorOperationException { try { return (DOMImplementationLS)DOMImplementationRegistry.newInstance().getDOMImplementation("LS"); } catch (Exception e) { throw new AuthorOperationException("Unexpected exception occured while instantiating DOM implementation: "+e.getMessage(), e); } } public static Document loadDocument(URL docUrl) throws AuthorOperationException { DOMImplementationLS impl = getDOMImplementationLS(); LSParser builder = impl.createLSParser(DOMImplementationLS.MODE_SYNCHRONOUS, null); LSInput input = impl.createLSInput(); try { input.setByteStream(docUrl.openStream()); } catch (IOException e) { throw new AuthorOperationException( "Could not load document from url"+docUrl.toString()+":\n"+e.getMessage(), e); } return builder.parse(input); } /** * De-serializes a xml document {@link String} representation to a {@link Document} * @param xml The xml to de-serailize * @param docUri The base uri of the de-serializes document * @return The de-serailized {@link Document} * @throws AuthorOperationException * When the given xml could not be de-serialized */ public static Document deserializeDocument(String xml, String docUri) throws AuthorOperationException { try { DOMImplementationLS impl = getDOMImplementationLS(); LSParser builder = impl.createLSParser(DOMImplementationLS.MODE_SYNCHRONOUS, null); LSInput input = impl.createLSInput(); input.setStringData(xml); Document res = builder.parse(input); if (docUri != null) { res.setDocumentURI(docUri); } return res; } catch (AuthorOperationException e) { throw e; } catch (Exception e) { throw new AuthorOperationException( "Unexpected exception occured while deserializing node from xml:\n"+xml+"\n"+e.getMessage(), e); } } /** * De-serializes a xml element {@link String} representation to a {@link Element} * @param xml The xml to de-serailize * @return The de-serailized {@link Element} * @throws AuthorOperationException * When the given xml could not be de-serialized */ public static Element deserializeElement(String xml) throws AuthorOperationException { return deserializeDocument(xml, null).getDocumentElement(); } /** * Gets the last child AuthorElement of a parent AuthorElement * @param parent The parent AuthorElement from which to get the last child * @return The last child or null if no last child exists */ public static AuthorElement getLastChild(AuthorElement parent) { List<AuthorNode> children = parent.getContentNodes(); for (int i = children.size()-1; i>=0; i--) { if (children.get(i) instanceof AuthorElement) return (AuthorElement)children.get(i); } return null; } /** * Gets the previous sibling of an AuthorElement * @param elem The AuthorElement for which to get the previous sibling * @return The previous sibling AuthorElement of elem - null if no previous sibling exists */ public static AuthorElement getPreviousSibling(AuthorElement elem) { if (elem.getParent()!=null) { AuthorElement parent = (AuthorElement)elem.getParent(); List<AuthorNode> siblings = parent.getContentNodes(); int thisIndex = siblings.indexOf(elem); for (int i=thisIndex-1; i>=0; i--) { if (siblings.get(i) instanceof AuthorElement) return (AuthorElement)siblings.get(i); } } return null; } /** * Gets the next sibling of an AuthorElement * @param elem The AuthorElement for which to get the next sibling * @return The next sibling AuthorElement of elem - null if no next sibling exists */ public static AuthorElement getNextSibling(AuthorElement elem) { if (elem.getParent()!=null) { AuthorElement parent = (AuthorElement)elem.getParent(); List<AuthorNode> siblings = parent.getContentNodes(); int thisIndex = siblings.indexOf(elem); for (int i=thisIndex+1; i<siblings.size(); i++) { if (siblings.get(i) instanceof AuthorElement) return (AuthorElement)siblings.get(i); } } return null; } /** * Recursively removes xmlns:xml attributes on an AuthorDocumentFragment and all it's decendants * @param input The AuthorDocumentFragment */ public static void removeXmlnsXmlAttribute(AuthorDocumentFragment input) { List<AuthorNode> nodes = input.getContentNodes(); for (int i=0; i<nodes.size(); i++) { removeXmlnsXmlAttribute(nodes.get(i)); } } /** * Recursively removes xmlns:xml attributes on an AuthorNode and all it's descendants * @param input The Node */ public static void removeXmlnsXmlAttribute(AuthorNode input) { if (input instanceof AuthorElement) { AuthorElement elem = (AuthorElement)input; elem.removeAttribute("xmlns:xml"); List<AuthorNode> children = elem.getContentNodes(); for (int i=0; i<children.size(); i++) { removeXmlnsXmlAttribute(children.get(i)); } } } /** * Serializes a {@link Node} to it's xml representation * @param input The {@link Node} to serailize * @return The serialized xml * @throws AuthorOperationException * When a {@link LSSerializer} for some reason could not be created */ public static String serialize(Node input) throws AuthorOperationException { LSSerializer writer = getDOMImplementationLS().createLSSerializer(); writer.getDomConfig().setParameter("xml-declaration", false); return writer.writeToString(input); } /** * Serialized a {@link AuthorNode}, optionally including all content nodes * @param authorAccess The AuthorAccess to use for serializing * @param input The {@link AuthorNode} to serialize * @param copyContent A {@link Boolean} indicating if content nodes should also be serailized * @return The serialized xml representation * @throws AuthorOperationException * When the given {@link AuthorNode} unexpectedly could not be serialized */ public static String serialize(AuthorAccess authorAccess, AuthorNode input, boolean copyContent) throws AuthorOperationException { AuthorDocumentController docCtrl = authorAccess.getDocumentController(); try { return docCtrl.serializeFragmentToXML(docCtrl.createDocumentFragment(input, copyContent)); } catch (BadLocationException e) { throw new AuthorOperationException( "Unexpected BadLocationException: "+e.getMessage(), e); } } /** * Serializes a {@link AuthorNode} to it's xml representation, including all content nodes * @param authorAccess The AuthorAccess to use for serializing * @param input The {@link AuthorNode} to serialize * @return The serialized xml representation * @throws AuthorOperationException * When the given {@link AuthorNode} unexpectedly could not be serialized */ public static String serialize(AuthorAccess authorAccess, AuthorNode input) throws AuthorOperationException { return serialize(authorAccess, input, true); } /** * Serializes the children of an AuthorElement to xml * @param authorAccess The AuthorAccess to use for serializing * @param elem The parent AuthorElement * @return The xml representing the children * @throws AuthorOperationException */ public static String serializeChildren(AuthorAccess authorAccess, AuthorElement elem) throws AuthorOperationException { String res = ""; List<AuthorNode> children = elem.getContentNodes(); for (int i=0; i<children.size(); i++) { res += serialize(authorAccess, children.get(i)); } return res; } /** * Serializes all children of a AuthorElement, including text nodes * @param authorAccess The AuthorAccess to use for serializing * @param aElem The parent AuthorElement * @return the serialized child nodes, an empty string of the AuthorElement is empty * @throws AuthorOperationException */ public static String serializeContent(AuthorAccess authorAccess, AuthorElement aElem) throws AuthorOperationException { Element elem = deserializeElement(serialize(authorAccess, aElem)); String res = ""; NodeList children = elem.getChildNodes(); for (int i=0; i<children.getLength(); i++) { res += serialize(children.item(i)); } return res; } public static void bringFocusToDocumentTab(AuthorAccess authorAccess) { bringFocusToDocumentTab(authorAccess.getEditorAccess()); } public static void bringFocusToDocumentTab(WSAuthorEditorPageBase editor) { Object compObject = editor.getAuthorComponent(); TabbedPaneChildComponentPair pair = getTabbedPaneAncestor((compObject instanceof Component ? (Component)compObject : null)); if (pair != null) { pair.TabbedPane.setSelectedComponent(pair.ChildComponent); } } private static class TabbedPaneChildComponentPair { public JTabbedPane TabbedPane; public Component ChildComponent; } private static TabbedPaneChildComponentPair getTabbedPaneAncestor(Component component) { if (component == null) return null; if (component.getParent() == null) return null; if (component.getParent() instanceof JTabbedPane) { TabbedPaneChildComponentPair res = new TabbedPaneChildComponentPair(); res.ChildComponent = component; res.TabbedPane = (JTabbedPane)component.getParent(); return res; } return getTabbedPaneAncestor(component.getParent()); } public static void replaceRoot(Document doc, AuthorAccess authorAccess) throws AuthorOperationException { String xml = serialize(doc); AuthorDocumentController ctrl = authorAccess.getDocumentController(); ctrl.beginCompoundEdit(); try { ctrl.replaceRoot(ctrl.createNewDocumentFragmentInContext(xml, 0)); AuthorDocument authorDoc = ctrl.getAuthorDocumentNode(); ctrl.getUniqueAttributesProcessor().assignUniqueIDs(authorDoc.getStartOffset(), authorDoc.getEndOffset(), false); } catch (Exception e) { ctrl.cancelCompoundEdit(); throw e; } ctrl.endCompoundEdit(); } public static AuthorElement getDecendantAuthorElementById(AuthorElement parent, String id) { if (id == null) return null; if (parent == null) return null; for (AuthorNode node : parent.getContentNodes()) { if (node instanceof AuthorElement) { AuthorElement elem = (AuthorElement)node; AttrValue val = elem.getAttribute("id"); if (val != null) { if (id.equals(val.getValue())) return elem; } elem = getDecendantAuthorElementById(elem, id); if (elem != null) return elem; } } return null; } public static Document getDOMDocument(AuthorAccess authorAccess) throws AuthorOperationException { if (authorAccess == null) return null; return deserializeDocument( serialize(authorAccess, authorAccess.getDocumentController().getAuthorDocumentNode()), authorAccess.getEditorAccess().getEditorLocation().toString()); } public static Element[] getChildElementsByNameNS(Element parent, String namespaceURI, String localName) { List<Element> res = new ArrayList<Element>(); NodeList nodes = parent.getChildNodes(); for (int i = 0; i < nodes.getLength(); i++) { Node node = nodes.item(i); if (node instanceof Element && node.getLocalName().equals(localName) && node.getNamespaceURI().equals(namespaceURI)) { res.add((Element)node); } } return res.toArray(new Element[0]); } public static Element getChildElementByNameNS(Element parent, String namespaceURI, String localName) { Element[] childElements = getChildElementsByNameNS(parent, namespaceURI, localName); if (childElements.length > 0) return childElements[0]; return null; } private static Pattern ZIP_URL_REGEX = Pattern.compile("^(zip:file:[^!]+!/)(.+)$"); public static String getUrlRelToZip(String url) { Matcher m = ZIP_URL_REGEX.matcher(url); return m.matches() ? ("/" + m.group(2)) : null; } public static URL getUrlRelToZip(URL url) { if (url == null) return null; try { return new URL(getUrlRelToZip(url.toString())); } catch (MalformedURLException e) { return null; } } public static String getZipUrl(String url) { String zipRoot = getZipRootUrl(url); if (zipRoot == null) return null; return zipRoot.substring(4, zipRoot.length()-2); } public static String getZipPath(String url) { String zipUrl = Utils.getZipUrl(url); if (zipUrl == null) return null; return new File(URI.create(zipUrl)).getAbsolutePath(); } public static String getZipRootUrl(String url) { Matcher m = ZIP_URL_REGEX.matcher(url); return m.matches() ? m.group(1) : null; } public static URL getZipRootUrl(URL url) { if (url == null) return null; try { return new URL(getZipRootUrl(url.toString())); } catch (MalformedURLException e) { return null; } } public static Dimension getImageDimension(URL imageURL) { Iterator<ImageReader> imageReaders = ImageIO.getImageReadersBySuffix(Utils.getExtension(imageURL.getPath())); if (imageReaders.hasNext()) { ImageReader reader = imageReaders.next(); try { File imageFile = new File(imageURL.toURI()); ImageInputStream imageStream = new FileImageInputStream(imageFile); try { reader.setInput(imageStream); return new Dimension( reader.getWidth(reader.getMinIndex()), reader.getHeight(reader.getMinIndex())); } finally { imageStream.close(); } } catch (IOException e) { return null; } catch (URISyntaxException e) { return null; } } return null; } public static String getExtension(String path) { int index = path.lastIndexOf('.'); if (index>-1 && index+1<path.length()) { return path.substring(index+1); } return ""; } public static String getOuterXml(Node n) { //For debug only... TransformerFactory transFactory = TransformerFactory.newInstance(); Transformer transformer = null; try { transformer = transFactory.newTransformer(); } catch (TransformerConfigurationException e1) { // TODO Auto-generated catch block e1.printStackTrace(); return ""; } try { StringWriter buffer = new StringWriter(); transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); transformer.setOutputProperty(OutputKeys.METHOD, "xml"); transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); transformer.transform(new DOMSource(n), new StreamResult(buffer)); return buffer.toString(); } catch (TransformerConfigurationException e) { // TODO Auto-generated catch block e.printStackTrace(); return ""; } catch (TransformerException e) { // TODO Auto-generated catch block e.printStackTrace(); return ""; } } }