Alternatives to XML?

Peter Otten __peter__ at web.de
Thu Aug 25 06:06:54 EDT 2016


Frank Millman wrote:

> "Chris Angelico"  wrote in message
> news:CAPTjJmof_sXqax0Ury5LsBEj7cdFv92WiWKbfvAC+bM=HwtHXA at mail.gmail.com...
> 
>> Sounds to me like you have two very different concerns, then. My
>> understanding of "GUI" is that it's a desktop app running on the user's
>> computer, as opposed to some sort of client/server system - am I right?
> 
> Not exactly, but the difference in not important, as you have got the
> essentials below spot on.
> 
> For the record, the server runs an HTTP server, and anyone on the LAN/WAN
> can access the system using a browser. Because the tables that define the
> database are stored in the database itself, there is no difference between
> a form that allows a user to capture an invoice, and a form that allows a
> user to modify a column definition. It is all controlled through
> permissions, but technically they are identical.
> 
>> 1) Malicious users, as I describe above, can simply mess with your code
>> directly, or bypass it and talk to the database, or anything. So you can
>> ignore them.
> 
> Absolutely. If an organisation running my system wants to be secure, they
> should keep the server in a locked room only accessible by a trusted
> system administrator.
> 
>> 2) Non-programmer users, without any sort of malice, want to be able to
>> edit these scripts but not be caught out by a tiny syntactic problem.
> 
> Now we are getting to the nitty-gritty.
> 
> [snip some good comments]
> 
>> Here's a very simple format, borrowing from RFC822 with a bit of Python
>> added:
> 
> if: _param.auto_party_id != None
>     if: on_insert
>         value: =auto_gen(_param.auto_party_id)
>     elif: not_exists
>         value: <new>
> 
> Getting close, but it is not *quite* that simple.
> 
> For example, having isolated the LHS of the if clause, I process it
> something like this -
> 
> if source.startswith("'"):
>     source_value = source[1:-1]
> elif '.' in source:
>     source_objname, source_colname = source.split('.', 1)
>     source_record = caller.data_objects[source_objname]
>     source_value = source_record.getval(source_colname)
> elif source == '$None':
>     source_value = None
> elif source == '$True':
>     source_value = True
> elif source == '$False':
>     source_value = False
> elif source.startswith('int('):
>     source_value = int(source[4:-1])

It may be a little more work, but whatever check you can think of for your 
XML can also be applied to a piece of Python code. For example the above 
might become

$ cat eval_source.py
import ast


def source_value_dot(source_objname, source_colname):
    return "<{}.{}>".format(source_objname, source_colname)
    # may become:
    source_record = caller.data_objects[source_objname]
    return source_record.getval(source_colname)


def get_source(node):
    # Modeled after ast.literal_eval()

    def _convert(node):
        if isinstance(node, ast.Expression):
            return _convert(node.body)
        elif isinstance(node, ast.Str):
            return node.s
        elif isinstance(node, ast.Attribute):
            if not isinstance(node.value, ast.Name):
                raise ValueError("only one dot, please", node)
            name = node.value.id
            column = node.attr
            return source_value_dot(name, column)
        elif isinstance(node, ast.NameConstant):
            return node.value
        elif isinstance(node, ast.Num):
            n = node.n
            if not isinstance(n, int):
                raise ValueError("only integers, please", node)
            return n
        else:
            raise ValueError("oops", node)
    return _convert(node)


EXAMPLES = """\
None
True
42
3.4
bar.baz
foo.bar.baz
'whatever.you.want'
""".splitlines()

for source in EXAMPLES:
    e = ast.parse(source, mode="eval")
    try:
        result = get_source(e)
    except ValueError as e:
        result = "#error: {}".format(e.args[0])
    print(source, "-->", result)

$ python3 eval_source.py 
None --> None
True --> True
42 --> 42
3.4 --> #error: only integers, please
bar.baz --> <bar.baz>
foo.bar.baz --> #error: only one dot, please
'whatever.you.want' --> whatever.you.want

Of course you could also use a slightly more general approach (use a 
whitelist of function names, feed dotted names to a function etc.), so that 
the resulting ast can safely be fed to exec().

Whatever you do, the most likely problem you may run into is annoyed users 
asking "This looks like Python, why can't I..."

> Anyway, you have isolated the essential issue. I need a DSL which is easy
> for a non-technical user to read/write, and easy to verify that it is
> achieving the desired result.
> 
> I suspect that this is quite challenging whatever format I use. Up to now
> I have been using XML, and it works for me. As Rob pointed out, I have
> become too comfortable with it to be objective, but no-one has yet
> convinced me that the alternatives are any better. I may eventually end up
> with an additional layer that prompts the user through their requirement
> in 'wizard' style, and generates the underlying XML (or whatever)
> automatically.
> 
> Frank





More information about the Python-list mailing list