/* * plist - An open source library to parse and generate property lists * Copyright (C) 2011 Daniel Dreibrodt * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.dd.plist; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; /** * A NSString contains a string. * * @author Daniel Dreibrodt */ public class NSString extends NSObject implements Comparable<Object> { private String content; /** * Creates an NSString from its binary representation. * * @param bytes The binary representation. * @param encoding The encoding of the binary representation, the name of a supported charset. * @throws UnsupportedEncodingException When the given encoding is not supported by the JRE. * @see java.lang.String#String(byte[], String) */ public NSString(byte[] bytes, String encoding) throws UnsupportedEncodingException { this(bytes, 0, bytes.length, encoding); } /** * Creates an NSString from its binary representation. * * @param bytes The binary representation. * @param startIndex int with the index where to start (offset) * @param endIndex int with the index where to stop reading (offset + string length) * @param encoding The encoding of the binary representation, the name of a supported charset. * @throws UnsupportedEncodingException When the given encoding is not supported by the JRE. * @see java.lang.String#String(byte[], String) */ public NSString(byte[] bytes, final int startIndex, final int endIndex, String encoding) throws UnsupportedEncodingException { content = new String(bytes, startIndex, endIndex - startIndex, encoding); } /** * Creates a NSString from a string. * * @param string The string that will be contained in the NSString. */ public NSString(String string) { content = string; } /** * Gets this strings content. * * @return This NSString as Java String object. */ public String getContent() { return content; } /** * Sets the contents of this string. * * @param c The new content of this string object. */ public void setContent(String c) { content = c; } /** * Appends a string to this string. * * @param s The string to append. */ public void append(NSString s) { append(s.getContent()); } /** * Appends a string to this string. * * @param s The string to append. */ public void append(String s) { content += s; } /** * Prepends a string to this string. * * @param s The string to prepend. */ public void prepend(String s) { content = s + content; } /** * Prepends a string to this string. * * @param s The string to prepend. */ public void prepend(NSString s) { prepend(s.getContent()); } @Override public boolean equals(Object obj) { if (!(obj instanceof NSString)) return false; return content.equals(((NSString) obj).content); } @Override public int hashCode() { return content.hashCode(); } /** * The textual representation of this NSString. * * @return The NSString's contents. */ @Override public String toString() { return content; } private static CharsetEncoder asciiEncoder, utf16beEncoder, utf8Encoder; @Override void toXML(StringBuilder xml, int level) { indent(xml, level); xml.append("<string>"); //Make sure that the string is encoded in UTF-8 for the XML output synchronized (NSString.class) { if (utf8Encoder == null) utf8Encoder = Charset.forName("UTF-8").newEncoder(); else utf8Encoder.reset(); try { ByteBuffer byteBuf = utf8Encoder.encode(CharBuffer.wrap(content)); byte[] bytes = new byte[byteBuf.remaining()]; byteBuf.get(bytes); content = new String(bytes, "UTF-8"); } catch (Exception ex) { throw new RuntimeException("Could not encode the NSString into UTF-8: " + String.valueOf(ex.getMessage())); } } //According to http://www.w3.org/TR/REC-xml/#syntax node values must not //contain the characters < or &. Also the > character should be escaped. if (content.contains("&") || content.contains("<") || content.contains(">")) { xml.append("<![CDATA["); xml.append(content.replaceAll("]]>", "]]]]><![CDATA[>")); xml.append("]]>"); } else { xml.append(content); } xml.append("</string>"); } @Override public void toBinary(BinaryPropertyListWriter out) throws IOException { CharBuffer charBuf = CharBuffer.wrap(content); int kind; ByteBuffer byteBuf; synchronized (NSString.class) { if (asciiEncoder == null) asciiEncoder = Charset.forName("ASCII").newEncoder(); else asciiEncoder.reset(); if (asciiEncoder.canEncode(charBuf)) { kind = 0x5; // standard ASCII byteBuf = asciiEncoder.encode(charBuf); } else { if (utf16beEncoder == null) utf16beEncoder = Charset.forName("UTF-16BE").newEncoder(); else utf16beEncoder.reset(); kind = 0x6; // UTF-16-BE byteBuf = utf16beEncoder.encode(charBuf); } } byte[] bytes = new byte[byteBuf.remaining()]; byteBuf.get(bytes); out.writeIntHeader(kind, content.length()); out.write(bytes); } @Override protected void toASCII(StringBuilder ascii, int level) { indent(ascii, level); ascii.append("\""); //According to https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/PropertyLists/OldStylePlists/OldStylePLists.html //non-ASCII characters are not escaped but simply written into the //file, thus actually violating the ASCII plain text format. //We will escape the string anyway because current Xcode project files (ASCII property lists) also escape their strings. ascii.append(escapeStringForASCII(content)); ascii.append("\""); } @Override protected void toASCIIGnuStep(StringBuilder ascii, int level) { indent(ascii, level); ascii.append("\""); ascii.append(escapeStringForASCII(content)); ascii.append("\""); } /** * Escapes a string for use in ASCII property lists. * * @param s The unescaped string. * @return The escaped string. */ static String escapeStringForASCII(String s) { String out = ""; char[] cArray = s.toCharArray(); for (int i = 0; i < cArray.length; i++) { char c = cArray[i]; if (c > 127) { //non-ASCII Unicode out += "\\U"; String hex = Integer.toHexString(c); while (hex.length() < 4) hex = "0" + hex; out += hex; } else if (c == '\\') { out += "\\\\"; } else if (c == '\"') { out += "\\\""; } else if (c == '\b') { out += "\\b"; } else if (c == '\n') { out += "\\n"; } else if (c == '\r') { out += "\\r"; } else if (c == '\t') { out += "\\t"; } else { out += c; } } return out; } public int compareTo(Object o) { if (o instanceof NSString) { return getContent().compareTo(((NSString) o).getContent()); } else if (o instanceof String) { return getContent().compareTo(((String) o)); } else { return -1; } } }