/******************************************************************************* * Copyright (c) 2012-2017 Codenvy, S.A. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ package org.eclipse.che.commons.xml; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import java.io.ByteArrayOutputStream; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; import static java.lang.Character.isWhitespace; import static java.lang.Math.min; import static java.lang.System.arraycopy; import static java.util.Arrays.fill; import static org.w3c.dom.Node.ELEMENT_NODE; /** * Utils for xml tree * * @author Eugene Voevodin */ public final class XMLTreeUtil { public static final Charset UTF_8 = Charset.forName("utf-8"); public static final int SPACES_IN_TAB = 4; /** * <pre> * New content schema: * * [0 - left) + content + (right, src.elementLength) * </pre> * * @param src * source array * @param left * left anchor - not included to result * @param right * right anchor - not included to result * @param content * content which will be inserted between left and right * @return new content */ public static byte[] insertBetween(byte[] src, int left, int right, String content) { final byte[] contentSrc = content.getBytes(UTF_8); final byte[] newSrc = new byte[left + src.length - right + contentSrc.length - 1]; arraycopy(src, 0, newSrc, 0, left); arraycopy(contentSrc, 0, newSrc, left, contentSrc.length); arraycopy(src, right + 1, newSrc, left + contentSrc.length, src.length - right - 1); return newSrc; } /** * <pre> * New content schema: * * [0 - pos) + content + [pos, src.elementLength) * </pre> * * @param src * source array * @param pos * start position for content insertion * @param content * content which will be inserted from {@param anchor} * @return new content */ public static byte[] insertInto(byte[] src, int pos, String content) { final byte[] contentSrc = content.getBytes(UTF_8); final byte[] newSrc = new byte[src.length + contentSrc.length]; arraycopy(src, 0, newSrc, 0, pos); arraycopy(contentSrc, 0, newSrc, pos, contentSrc.length); arraycopy(src, pos, newSrc, pos + contentSrc.length, src.length - pos); return newSrc; } /** * Check given list contains only element and return it. * If list size is not 1 {@link XMLTreeException} will be thrown. * * @param target * list to check * @return list only element */ public static <T> T single(List<T> target) { if (target.size() != 1) { throw new XMLTreeException("Required list with one element"); } return target.get(0); } public static int lastIndexOf(byte[] src, char c, int fromIdx) { for (int i = min(fromIdx, src.length - 1); i >= 0; i--) { if (src[i] == c) { return i; } } return -1; } /** * Calculates how deep is the element in tree. * First level is tree root and it is equal to 0. * * @param element * target element * @return how deep is the element */ public static int level(Element element) { int level = 0; while (element.hasParent()) { element = element.getParent(); level++; } return level; } public static int openTagLength(NewElement element) { int len = 2; // '<' + '>' len += element.getName().length();// 'name' or 'prefix:name' for (NewAttribute attribute : element.getAttributes()) { len += 1 + attributeLength(attribute); // ' ' + 'attr="value"' or 'pref:attr="value"' } //if is void add +1 '/' return element.isVoid() ? len + 1 : len; } public static int closeTagLength(NewElement element) { return 3 + element.getLocalName().length(); // '<' + '/' + 'name' + '>' } public static int attributeLength(NewAttribute attribute) { int len = 0; if (attribute.hasPrefix()) { len += attribute.getPrefix().length() + 1; //prefix + ':' } len += attribute.getName().length() + attribute.getValue().length() + 3; return len; } /** * Inserts given number of tabs to each line of given source. * * @param src * source which going to be tabulated * @param tabsCount * how many tabs should be added before each line * @return tabulated source */ public static String tabulate(String src, int tabsCount) { char[] tabs = new char[SPACES_IN_TAB * tabsCount]; fill(tabs, ' '); final StringBuilder builder = new StringBuilder(); final String[] lines = src.split("\n"); for (int i = 0; i < lines.length - 1; i++) { builder.append(tabs) .append(lines[i]) .append('\n'); } builder.append(tabs) .append(lines[lines.length - 1]); return builder.toString(); } /** * Fetches {@link Element} from {@link Node} using {@link Node#getUserData(String)} * * @param node * node to fetch from * @return {@code null} if {@param node} is null or {@link Element} associated with given node */ public static Element asElement(Node node) { if (node == null) { return null; } return (Element)node.getUserData("element"); } /** * Converts {@link NodeList} to list of elements. * Only nodes with type {@link Node#ELEMENT_NODE} will * be fetched other will be skipped * * @param list * list of nodes to fetch elements from * @return list of fetched elements or empty list if node list doesn't contain any element node */ public static List<Element> asElements(NodeList list) { final List<Element> elements = new ArrayList<>(list.getLength()); for (int i = 0; i < list.getLength(); i++) { if (list.item(i).getNodeType() == ELEMENT_NODE) { elements.add(asElement(list.item(i))); } } return elements; } public static <R> List<R> asElements(NodeList list, ElementMapper<? extends R> mapper) { final List<R> elements = new ArrayList<>(list.getLength()); for (int i = 0; i < list.getLength(); i++) { if (list.item(i).getNodeType() == ELEMENT_NODE) { elements.add(mapper.map(asElement(list.item(i)))); } } return elements; } /** * Searches for target bytes in the source bytes. * * @param src * where to search * @param target * what to search * @param fromIdx * source index to search from * @return index of the first occurrence or -1 if nothing was found */ public static int indexOf(byte[] src, byte[] target, int fromIdx) { final int to = src.length - target.length + 1; for (int i = fromIdx; i < to; i++) { if (src[i] == target[0]) { boolean equals = true; for (int j = 1, k = i + 1; j < target.length && equals; j++, k++) { if (src[k] != target[j]) { equals = false; } } if (equals) { return i; } } } return -1; } /** * Search for attribute name bytes in the source bytes. * The main difference with {@link #indexOf} is that * if occurrence was found then we need to check next byte * as character, if it is whitespace character or it equals to '=' * attribute name was found otherwise continue searching * * @param src * where to search * @param target * attribute name bytes to search * @param fromIdx * source index to search from * @return index of the first attribute name occurrence or -1 if nothing was found */ public static int indexOfAttributeName(byte[] src, byte[] target, int fromIdx) { final int idx = indexOf(src, target, fromIdx); if (idx == -1) { return -1; } final int next = idx + target.length; if (next == src.length || isWhitespace(src[next]) || src[next] == '=') { return idx; } return indexOfAttributeName(src, target, idx + 1); } public static byte[] replaceAll(byte[] src, byte[] target, byte[] replacement) { final ByteArrayOutputStream result = new ByteArrayOutputStream(src.length); int i = 0; int wrote = 0; while ((i = indexOf(src, target, i)) != -1) { int len = i - wrote; result.write(src, wrote, len); result.write(replacement, 0, replacement.length); wrote += len + target.length; i += target.length; } result.write(src, wrote, src.length - wrote); return result.toByteArray(); } public static int rootStart(byte[] xml) { final byte[] open = {'<'}; int pos = indexOf(xml, open, 0); while (xml[pos + 1] == '?' || xml[pos + 1] == '!') { if (xml[pos + 1] == '!' && xml[pos + 2] == '-' && xml[pos + 3] == '-') { pos = indexOf(xml, new byte[] {'-', '-', '>'}, pos + 1); } pos = indexOf(xml, open, pos + 1); } return pos; } private XMLTreeUtil() { } }