package org.python.core.stringlib; import org.python.core.Py; import org.python.core.PyObject; import org.python.core.PyString; import org.python.core.PyTuple; import org.python.core.PyType; import org.python.expose.ExposedMethod; import org.python.expose.ExposedType; /** * Provides an implementation of str._formatter_parser() */ @ExposedType(name = "formatteriterator", base = PyObject.class, isBaseType = false) public class MarkupIterator extends PyObject { public static final PyType TYPE = PyType.fromClass(MarkupIterator.class); private final String markup; private int index; public MarkupIterator(String markup) { this.markup = markup; } @Override public PyObject __iter__() { return formatteriterator___iter__(); } @ExposedMethod final PyObject formatteriterator___iter__() { return this; } @Override public PyObject __iternext__() { return formatteriterator___iternext__(); } @ExposedMethod final PyObject formatteriterator___iternext__() { Chunk chunk; try { chunk = nextChunk(); } catch (IllegalArgumentException e) { throw Py.ValueError(e.getMessage()); } if (chunk == null) { return null; } PyObject[] elements = new PyObject[4]; elements[0] = new PyString(chunk.literalText); elements[1] = new PyString(chunk.fieldName); if (chunk.fieldName.length() > 0) { elements[2] = chunk.formatSpec == null ? Py.EmptyString : new PyString(chunk.formatSpec); } else { elements[2] = Py.None; } elements[3] = chunk.conversion == null ? Py.None : new PyString(chunk.conversion); return new PyTuple(elements); } public Chunk nextChunk() { if (index == markup.length()) { return null; } Chunk result = new Chunk(); int pos = index; while (true) { pos = indexOfFirst(markup, pos, '{', '}'); if (pos >= 0 && pos < markup.length() - 1 && markup.charAt(pos + 1) == markup.charAt(pos)) { // skip escaped bracket pos += 2; } else if (pos >= 0 && markup.charAt(pos) == '}') { throw new IllegalArgumentException("Single '}' encountered in format string"); } else { break; } } if (pos < 0) { result.literalText = unescapeBraces(markup.substring(index)); result.fieldName = ""; index = markup.length(); } else { result.literalText = unescapeBraces(markup.substring(index, pos)); pos++; int fieldStart = pos; int count = 1; while (pos < markup.length()) { if (markup.charAt(pos) == '{') { count++; result.formatSpecNeedsExpanding = true; } else if (markup.charAt(pos) == '}') { count--; if (count == 0) { parseField(result, markup.substring(fieldStart, pos)); pos++; break; } } pos++; } if (count > 0) { throw new IllegalArgumentException("Single '{' encountered in format string"); } index = pos; } return result; } private String unescapeBraces(String substring) { return substring.replace("{{", "{").replace("}}", "}"); } private void parseField(Chunk result, String fieldMarkup) { int pos = indexOfFirst(fieldMarkup, 0, '!', ':'); if (pos >= 0) { result.fieldName = fieldMarkup.substring(0, pos); if (fieldMarkup.charAt(pos) == '!') { if (pos == fieldMarkup.length() - 1) { throw new IllegalArgumentException("end of format while " + "looking for conversion specifier"); } result.conversion = fieldMarkup.substring(pos + 1, pos + 2); pos += 2; if (pos < fieldMarkup.length()) { if (fieldMarkup.charAt(pos) != ':') { throw new IllegalArgumentException("expected ':' " + "after conversion specifier"); } result.formatSpec = fieldMarkup.substring(pos + 1); } } else { result.formatSpec = fieldMarkup.substring(pos + 1); } } else { result.fieldName = fieldMarkup; } } private int indexOfFirst(String s, int start, char c1, char c2) { int i1 = s.indexOf(c1, start); int i2 = s.indexOf(c2, start); if (i1 == -1) { return i2; } if (i2 == -1) { return i1; } return Math.min(i1, i2); } public static final class Chunk { public String literalText; public String fieldName; public String formatSpec; public String conversion; public boolean formatSpecNeedsExpanding; } }