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.core.PyUnicode;
import org.python.core.Untraversable;
import org.python.expose.ExposedMethod;
import org.python.expose.ExposedType;

/**
 * Provides an implementation of the object that str._formatter_parser() returns, which
 * is an iterator returning successive 4-tuples, the sequence being equivalent to the original
 * string.
 */
@Untraversable
@ExposedType(name = "formatteriterator", base = PyObject.class, isBaseType = false)
public class MarkupIterator extends PyObject {

    public static final PyType TYPE = PyType.fromClass(MarkupIterator.class);

    /** The UTF-16 string from which elements are being returned. */
    private final String markup;
    /** True if originally given a PyString (so must return PyString not PyUnicode). */
    private final boolean bytes;
    /** How far along that string we are. */
    private int index;
    /** A counter used to auto-number fields when not explicitly numbered in the format. */
    private final FieldNumbering numbering;

    /** Constructor used at top-level to enumerate a format. */
    public MarkupIterator(PyString markupObject) {
        markup = markupObject.getString();
        bytes = !(markupObject instanceof PyUnicode);
        numbering = new FieldNumbering();
    }

    /** Variant constructor used when formats are nested. */
    public MarkupIterator(MarkupIterator enclosingIterator, String subMarkup) {
        markup = subMarkup;
        bytes = enclosingIterator.bytes;
        numbering = enclosingIterator.numbering;
    }

    @Override
    public PyObject __iter__() {
        return formatteriterator___iter__();
    }

    @ExposedMethod
    final PyObject formatteriterator___iter__() {
        return this;
    }

    @Override
    public PyObject __iternext__() {
        return formatteriterator___iternext__();
    }

    /**
     * Return the next "chunk" of the format (or return null if ended). A chunk is a 4-tuple
     * describing
     * 
    *
  1. the text leading up to the next format field,
  2. *
  3. the field name or number (as a string) for accessing the value,
  4. *
  5. the format specifier such as "#12x", and
  6. *
  7. any conversion that should be applied (the 's' or 'r' codes for * str() and repr())
  8. *
* Elements 1-3 are None if this chunk contains no format specifier. Elements 0-2 are * zero-length strings if missing from the format, while element 3 will be None if missing. * * @return PyTuple chunk or null */ @ExposedMethod final PyObject formatteriterator___iternext__() { try { // Parse off the next literal text and replacement field Chunk chunk = nextChunk(); if (chunk != null) { // Result will be built here PyObject[] elements = new PyObject[4]; // Literal text is used verbatim. elements[0] = wrap(chunk.literalText, ""); if (chunk.fieldName == null) { // A field name is null only if there was no replacement field at all. for (int i = 1; i < elements.length; i++) { elements[i] = Py.None; } } else { // Otherwise, this is the field name elements[1] = wrap(chunk.fieldName, ""); // The format spec may be blank elements[2] = wrap(chunk.formatSpec, ""); // There may have been a conversion specifier (if not, None is signalled). elements[3] = wrap(chunk.conversion, null); } // And those make up the next answer. return new PyTuple(elements); } else { // End of format: end of iteration return null; } } catch (IllegalArgumentException e) { throw Py.ValueError(e.getMessage()); } } /** * Convenience method for populating the return tuple, returning a PyString or * PyUnicode according to the type of the original markup string, or * Py.None if both arguments are null. * * @param value to wrap as a PyObject or null if defaultValue should be wrapped. * @param defaultValue to return or null if default return is None. * @return object for tuple */ private PyObject wrap(String value, String defaultValue) { if (value == null) { value = defaultValue; } if (value == null) { // It's still null, we want a None return Py.None; } else if (value.length() == 0) { // This is frequent so avoid the constructor return bytes ? Py.EmptyString : Py.EmptyUnicode; } else { return bytes ? Py.newString(value) : Py.newUnicode(value); } } /** * Return the next {@link Chunk} from the iterator, which is a structure containing parsed * elements of the replacement field (if any), and its preceding text. This is the Java * equivalent of the tuple returned by {@link #__iternext__()}. This finds use in the * implementation of str.format and unicode.format. * * @return the chunk */ public Chunk nextChunk() { if (index == markup.length()) { return null; } Chunk result = new Chunk(); // pos = index is the index of the first text not already chunked int pos = index; // Advance pos to the first '{' that is not a "{{" (escaped brace), or pos<0 if none such. 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) == '}') { // Un-escaped '}' is a syntax error throw new IllegalArgumentException("Single '}' encountered in format string"); } else { // pos is at an un-escaped '{' break; } } // markup[index:pos] is the literal part of this chunk. if (pos < 0) { // ... except pos<0, and there is no further format specifier, only literal text. result.literalText = unescapeBraces(markup.substring(index)); index = markup.length(); } else { // Grab the literal text, dealing with escaped braces. result.literalText = unescapeBraces(markup.substring(index, pos)); // Scan through the contents of the format spec, between the braces. Skip one '{'. pos++; int fieldStart = pos; int count = 1; while (pos < markup.length()) { if (markup.charAt(pos) == '{') { // This means the spec we are gathering itself contains nested specifiers. count++; result.formatSpecNeedsExpanding = true; } else if (markup.charAt(pos) == '}') { // And here is a '}' matching one we already counted. count--; if (count == 0) { // ... matching the one we began with: parse the replacement field. parseField(result, markup.substring(fieldStart, pos)); pos++; break; } } pos++; } if (count > 0) { // Must be end of string without matching '}'. throw new IllegalArgumentException("Single '{' encountered in format string"); } index = pos; } return result; } /** * If originally given a PyString, string elements in the returned tuples must be PyString not * PyUnicode. * * @return true if originally given a PyString */ public final boolean isBytes() { return bytes; } private String unescapeBraces(String substring) { return substring.replace("{{", "{").replace("}}", "}"); } /** * Parse a "replacement field" consisting of a name, conversion and format specification. * According to the Python Standard Library documentation, a replacement field has the * structure: * *
     * replacement_field ::=  "{" [field_name] ["!" conversion] [":" format_spec] "}"
     * field_name        ::=  arg_name ("." attribute_name | "[" element_index "]")*
     * arg_name          ::=  [identifier | integer]
     * attribute_name    ::=  identifier
     * element_index     ::=  integer | index_string
     * 
* * except at this point, we have already discarded the outer braces. * * @param result destination chunk * @param fieldMarkup specifying a replacement field, possibly with nesting */ private void parseField(Chunk result, String fieldMarkup) { int pos = indexOfFirst(fieldMarkup, 0, '!', ':'); if (pos >= 0) { // There's a '!' or a ':', so what precedes the first of them is a field name. result.fieldName = fieldMarkup.substring(0, pos); if (fieldMarkup.charAt(pos) == '!') { // There's a conversion specifier 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; // And if that's not the end, there ought to be a ':' now. if (pos < fieldMarkup.length()) { if (fieldMarkup.charAt(pos) != ':') { throw new IllegalArgumentException("expected ':' " + "after conversion specifier"); } // So the format specifier is from the ':' to the end. result.formatSpec = fieldMarkup.substring(pos + 1); } } else { // No '!', so the format specifier is from the ':' to the end. Or empty. result.formatSpec = fieldMarkup.substring(pos + 1); } } else { // Neither a '!' nor a ':', the whole thing is a name. result.fieldName = fieldMarkup; } if (result.fieldName.isEmpty()) { // The field was empty, so generate a number automatically. result.fieldName = numbering.nextAutomaticFieldNumber(); return; } // Automatic numbers must also work when there is an .attribute or [index] char c = result.fieldName.charAt(0); if (c == '.' || c == '[') { result.fieldName = numbering.nextAutomaticFieldNumber() + result.fieldName; return; } // Finally, remember the argument number was specified (perhaps complain of mixed use) if (Character.isDigit(c)) { numbering.useManualFieldNumbering(); } } /** Find the first of two characters, or return -1. */ 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); } /** * Class used locally to assign indexes to the automatically-numbered arguments (see String * Formatting section of the Python Standard Library). */ static final class FieldNumbering { private boolean manualFieldNumberSpecified; private int automaticFieldNumber = 0; /** * Generate a numeric argument index automatically, or raise an error if already started * numbering manually. * * @return index as string */ String nextAutomaticFieldNumber() { if (manualFieldNumberSpecified) { throw new IllegalArgumentException( "cannot switch from manual field specification to automatic field numbering"); } return Integer.toString(automaticFieldNumber++); } /** * Remember we are numbering manually, and raise an error if already started numbering * automatically. */ void useManualFieldNumbering() { if (manualFieldNumberSpecified) { return; } if (automaticFieldNumber != 0) { throw new IllegalArgumentException( "cannot switch from automatic field numbering to manual field specification"); } manualFieldNumberSpecified = true; } } public static final class Chunk { /** The text leading up to the next format field. */ public String literalText; /** The field name or number (as a string) for accessing the value. */ public String fieldName; /** The format specifier such as "#12x". */ public String formatSpec; /** Conversion to be applied, e.g. 'r' for repr(). */ public String conversion; /** Signals the formatSpec needs expanding recursively. */ public boolean formatSpecNeedsExpanding; } }

Ads help maintain this website.