# -*- coding: utf-8 -*-"""Parse tokens from the lexer into nodes for the compiler."""from . import nodesfrom ._compat import imapfrom .exceptions import TemplateAssertionErrorfrom .exceptions import TemplateSyntaxErrorfrom .lexer import describe_tokenfrom .lexer import describe_token_expr_statement_keywords = frozenset(["for","if","block","extends","print","macro","include","from","import","set","with","autoescape",])_compare_operators = frozenset(["eq", "ne", "lt", "lteq", "gt", "gteq"])_math_nodes = {"add": nodes.Add,"sub": nodes.Sub,"mul": nodes.Mul,"div": nodes.Div,"floordiv": nodes.FloorDiv,"mod": nodes.Mod,}class Parser(object):"""This is the central parsing class Jinja uses. It's passed toextensions and can be used to parse expressions or statements."""def __init__(self, environment, source, name=None, filename=None, state=None):self.environment = environmentself.stream = environment._tokenize(source, name, filename, state)self.name = nameself.filename = filenameself.closed = Falseself.extensions = {}for extension in environment.iter_extensions():for tag in extension.tags:self.extensions[tag] = extension.parseself._last_identifier = 0self._tag_stack = []self._end_token_stack = []def fail(self, msg, lineno=None, exc=TemplateSyntaxError):"""Convenience method that raises `exc` with the message, passedline number or last line number as well as the current name andfilename."""if lineno is None:lineno = self.stream.current.linenoraise exc(msg, lineno, self.name, self.filename)def _fail_ut_eof(self, name, end_token_stack, lineno):expected = []for exprs in end_token_stack:expected.extend(imap(describe_token_expr, exprs))if end_token_stack:currently_looking = " or ".join("'%s'" % describe_token_expr(expr) for expr in end_token_stack[-1])else:currently_looking = Noneif name is None:message = ["Unexpected end of template."]else:message = ["Encountered unknown tag '%s'." % name]if currently_looking:if name is not None and name in expected:message.append("You probably made a nesting mistake. Jinja ""is expecting this tag, but currently looking ""for %s." % currently_looking)else:message.append("Jinja was looking for the following tags: ""%s." % currently_looking)if self._tag_stack:message.append("The innermost block that needs to be ""closed is '%s'." % self._tag_stack[-1])self.fail(" ".join(message), lineno)def fail_unknown_tag(self, name, lineno=None):"""Called if the parser encounters an unknown tag. Tries to failwith a human readable error message that could help to identifythe problem."""return self._fail_ut_eof(name, self._end_token_stack, lineno)def fail_eof(self, end_tokens=None, lineno=None):"""Like fail_unknown_tag but for end of template situations."""stack = list(self._end_token_stack)if end_tokens is not None:stack.append(end_tokens)return self._fail_ut_eof(None, stack, lineno)def is_tuple_end(self, extra_end_rules=None):"""Are we at the end of a tuple?"""if self.stream.current.type in ("variable_end", "block_end", "rparen"):return Trueelif extra_end_rules is not None:return self.stream.current.test_any(extra_end_rules)return Falsedef free_identifier(self, lineno=None):"""Return a new free identifier as :class:`~jinja2.nodes.InternalName`."""self._last_identifier += 1rv = object.__new__(nodes.InternalName)nodes.Node.__init__(rv, "fi%d" % self._last_identifier, lineno=lineno)return rvdef parse_statement(self):"""Parse a single statement."""token = self.stream.currentif token.type != "name":self.fail("tag name expected", token.lineno)self._tag_stack.append(token.value)pop_tag = Truetry:if token.value in _statement_keywords:return getattr(self, "parse_" + self.stream.current.value)()if token.value == "call":return self.parse_call_block()if token.value == "filter":return self.parse_filter_block()ext = self.extensions.get(token.value)if ext is not None:return ext(self)# did not work out, remove the token we pushed by accident# from the stack so that the unknown tag fail function can# produce a proper error message.self._tag_stack.pop()pop_tag = Falseself.fail_unknown_tag(token.value, token.lineno)finally:if pop_tag:self._tag_stack.pop()def parse_statements(self, end_tokens, drop_needle=False):"""Parse multiple statements into a list until one of the end tokensis reached. This is used to parse the body of statements as it alsoparses template data if appropriate. The parser checks first if thecurrent token is a colon and skips it if there is one. Then it checksfor the block end and parses until if one of the `end_tokens` isreached. Per default the active token in the stream at the end ofthe call is the matched end token. If this is not wanted `drop_needle`can be set to `True` and the end token is removed."""# the first token may be a colon for python compatibilityself.stream.skip_if("colon")# in the future it would be possible to add whole code sections# by adding some sort of end of statement token and parsing those here.self.stream.expect("block_end")result = self.subparse(end_tokens)# we reached the end of the template too early, the subparser# does not check for this, so we do that nowif self.stream.current.type == "eof":self.fail_eof(end_tokens)if drop_needle:next(self.stream)return resultdef parse_set(self):"""Parse an assign statement."""lineno = next(self.stream).linenotarget = self.parse_assign_target(with_namespace=True)if self.stream.skip_if("assign"):expr = self.parse_tuple()return nodes.Assign(target, expr, lineno=lineno)filter_node = self.parse_filter(None)body = self.parse_statements(("name:endset",), drop_needle=True)return nodes.AssignBlock(target, filter_node, body, lineno=lineno)def parse_for(self):"""Parse a for loop."""lineno = self.stream.expect("name:for").linenotarget = self.parse_assign_target(extra_end_rules=("name:in",))self.stream.expect("name:in")iter = self.parse_tuple(with_condexpr=False, extra_end_rules=("name:recursive",))test = Noneif self.stream.skip_if("name:if"):test = self.parse_expression()recursive = self.stream.skip_if("name:recursive")body = self.parse_statements(("name:endfor", "name:else"))if next(self.stream).value == "endfor":else_ = []else:else_ = self.parse_statements(("name:endfor",), drop_needle=True)return nodes.For(target, iter, body, else_, test, recursive, lineno=lineno)def parse_if(self):"""Parse an if construct."""node = result = nodes.If(lineno=self.stream.expect("name:if").lineno)while 1:node.test = self.parse_tuple(with_condexpr=False)node.body = self.parse_statements(("name:elif", "name:else", "name:endif"))node.elif_ = []node.else_ = []token = next(self.stream)if token.test("name:elif"):node = nodes.If(lineno=self.stream.current.lineno)result.elif_.append(node)continueelif token.test("name:else"):result.else_ = self.parse_statements(("name:endif",), drop_needle=True)breakreturn resultdef parse_with(self):node = nodes.With(lineno=next(self.stream).lineno)targets = []values = []while self.stream.current.type != "block_end":if targets:self.stream.expect("comma")target = self.parse_assign_target()target.set_ctx("param")targets.append(target)self.stream.expect("assign")values.append(self.parse_expression())node.targets = targetsnode.values = valuesnode.body = self.parse_statements(("name:endwith",), drop_needle=True)return nodedef parse_autoescape(self):node = nodes.ScopedEvalContextModifier(lineno=next(self.stream).lineno)node.options = [nodes.Keyword("autoescape", self.parse_expression())]node.body = self.parse_statements(("name:endautoescape",), drop_needle=True)return nodes.Scope([node])def parse_block(self):node = nodes.Block(lineno=next(self.stream).lineno)node.name = self.stream.expect("name").valuenode.scoped = self.stream.skip_if("name:scoped")# common problem people encounter when switching from django# to jinja. we do not support hyphens in block names, so let's# raise a nicer error message in that case.if self.stream.current.type == "sub":self.fail("Block names in Jinja have to be valid Python ""identifiers and may not contain hyphens, use an ""underscore instead.")node.body = self.parse_statements(("name:endblock",), drop_needle=True)self.stream.skip_if("name:" + node.name)return nodedef parse_extends(self):node = nodes.Extends(lineno=next(self.stream).lineno)node.template = self.parse_expression()return nodedef parse_import_context(self, node, default):if self.stream.current.test_any("name:with", "name:without") and self.stream.look().test("name:context"):node.with_context = next(self.stream).value == "with"self.stream.skip()else:node.with_context = defaultreturn nodedef parse_include(self):node = nodes.Include(lineno=next(self.stream).lineno)node.template = self.parse_expression()if self.stream.current.test("name:ignore") and self.stream.look().test("name:missing"):node.ignore_missing = Trueself.stream.skip(2)else:node.ignore_missing = Falsereturn self.parse_import_context(node, True)def parse_import(self):node = nodes.Import(lineno=next(self.stream).lineno)node.template = self.parse_expression()self.stream.expect("name:as")node.target = self.parse_assign_target(name_only=True).namereturn self.parse_import_context(node, False)def parse_from(self):node = nodes.FromImport(lineno=next(self.stream).lineno)node.template = self.parse_expression()self.stream.expect("name:import")node.names = []def parse_context():if self.stream.current.value in ("with","without",) and self.stream.look().test("name:context"):node.with_context = next(self.stream).value == "with"self.stream.skip()return Truereturn Falsewhile 1:if node.names:self.stream.expect("comma")if self.stream.current.type == "name":if parse_context():breaktarget = self.parse_assign_target(name_only=True)if target.name.startswith("_"):self.fail("names starting with an underline can not be imported",target.lineno,exc=TemplateAssertionError,)if self.stream.skip_if("name:as"):alias = self.parse_assign_target(name_only=True)node.names.append((target.name, alias.name))else:node.names.append(target.name)if parse_context() or self.stream.current.type != "comma":breakelse:self.stream.expect("name")if not hasattr(node, "with_context"):node.with_context = Falsereturn nodedef parse_signature(self, node):node.args = args = []node.defaults = defaults = []self.stream.expect("lparen")while self.stream.current.type != "rparen":if args:self.stream.expect("comma")arg = self.parse_assign_target(name_only=True)arg.set_ctx("param")if self.stream.skip_if("assign"):defaults.append(self.parse_expression())elif defaults:self.fail("non-default argument follows default argument")args.append(arg)self.stream.expect("rparen")def parse_call_block(self):node = nodes.CallBlock(lineno=next(self.stream).lineno)if self.stream.current.type == "lparen":self.parse_signature(node)else:node.args = []node.defaults = []node.call = self.parse_expression()if not isinstance(node.call, nodes.Call):self.fail("expected call", node.lineno)node.body = self.parse_statements(("name:endcall",), drop_needle=True)return nodedef parse_filter_block(self):node = nodes.FilterBlock(lineno=next(self.stream).lineno)node.filter = self.parse_filter(None, start_inline=True)node.body = self.parse_statements(("name:endfilter",), drop_needle=True)return nodedef parse_macro(self):node = nodes.Macro(lineno=next(self.stream).lineno)node.name = self.parse_assign_target(name_only=True).nameself.parse_signature(node)node.body = self.parse_statements(("name:endmacro",), drop_needle=True)return nodedef parse_print(self):node = nodes.Output(lineno=next(self.stream).lineno)node.nodes = []while self.stream.current.type != "block_end":if node.nodes:self.stream.expect("comma")node.nodes.append(self.parse_expression())return nodedef parse_assign_target(self,with_tuple=True,name_only=False,extra_end_rules=None,with_namespace=False,):"""Parse an assignment target. As Jinja allows assignments totuples, this function can parse all allowed assignment targets. Perdefault assignments to tuples are parsed, that can be disable howeverby setting `with_tuple` to `False`. If only assignments to names arewanted `name_only` can be set to `True`. The `extra_end_rules`parameter is forwarded to the tuple parsing function. If`with_namespace` is enabled, a namespace assignment may be parsed."""if with_namespace and self.stream.look().type == "dot":token = self.stream.expect("name")next(self.stream) # dotattr = self.stream.expect("name")target = nodes.NSRef(token.value, attr.value, lineno=token.lineno)elif name_only:token = self.stream.expect("name")target = nodes.Name(token.value, "store", lineno=token.lineno)else:if with_tuple:target = self.parse_tuple(simplified=True, extra_end_rules=extra_end_rules)else:target = self.parse_primary()target.set_ctx("store")if not target.can_assign():self.fail("can't assign to %r" % target.__class__.__name__.lower(), target.lineno)return targetdef parse_expression(self, with_condexpr=True):"""Parse an expression. Per default all expressions are parsed, ifthe optional `with_condexpr` parameter is set to `False` conditionalexpressions are not parsed."""if with_condexpr:return self.parse_condexpr()return self.parse_or()def parse_condexpr(self):lineno = self.stream.current.linenoexpr1 = self.parse_or()while self.stream.skip_if("name:if"):expr2 = self.parse_or()if self.stream.skip_if("name:else"):expr3 = self.parse_condexpr()else:expr3 = Noneexpr1 = nodes.CondExpr(expr2, expr1, expr3, lineno=lineno)lineno = self.stream.current.linenoreturn expr1def parse_or(self):lineno = self.stream.current.linenoleft = self.parse_and()while self.stream.skip_if("name:or"):right = self.parse_and()left = nodes.Or(left, right, lineno=lineno)lineno = self.stream.current.linenoreturn leftdef parse_and(self):lineno = self.stream.current.linenoleft = self.parse_not()while self.stream.skip_if("name:and"):right = self.parse_not()left = nodes.And(left, right, lineno=lineno)lineno = self.stream.current.linenoreturn leftdef parse_not(self):if self.stream.current.test("name:not"):lineno = next(self.stream).linenoreturn nodes.Not(self.parse_not(), lineno=lineno)return self.parse_compare()def parse_compare(self):lineno = self.stream.current.linenoexpr = self.parse_math1()ops = []while 1:token_type = self.stream.current.typeif token_type in _compare_operators:next(self.stream)ops.append(nodes.Operand(token_type, self.parse_math1()))elif self.stream.skip_if("name:in"):ops.append(nodes.Operand("in", self.parse_math1()))elif self.stream.current.test("name:not") and self.stream.look().test("name:in"):self.stream.skip(2)ops.append(nodes.Operand("notin", self.parse_math1()))else:breaklineno = self.stream.current.linenoif not ops:return exprreturn nodes.Compare(expr, ops, lineno=lineno)def parse_math1(self):lineno = self.stream.current.linenoleft = self.parse_concat()while self.stream.current.type in ("add", "sub"):cls = _math_nodes[self.stream.current.type]next(self.stream)right = self.parse_concat()left = cls(left, right, lineno=lineno)lineno = self.stream.current.linenoreturn leftdef parse_concat(self):lineno = self.stream.current.linenoargs = [self.parse_math2()]while self.stream.current.type == "tilde":next(self.stream)args.append(self.parse_math2())if len(args) == 1:return args[0]return nodes.Concat(args, lineno=lineno)def parse_math2(self):lineno = self.stream.current.linenoleft = self.parse_pow()while self.stream.current.type in ("mul", "div", "floordiv", "mod"):cls = _math_nodes[self.stream.current.type]next(self.stream)right = self.parse_pow()left = cls(left, right, lineno=lineno)lineno = self.stream.current.linenoreturn leftdef parse_pow(self):lineno = self.stream.current.linenoleft = self.parse_unary()while self.stream.current.type == "pow":next(self.stream)right = self.parse_unary()left = nodes.Pow(left, right, lineno=lineno)lineno = self.stream.current.linenoreturn leftdef parse_unary(self, with_filter=True):token_type = self.stream.current.typelineno = self.stream.current.linenoif token_type == "sub":next(self.stream)node = nodes.Neg(self.parse_unary(False), lineno=lineno)elif token_type == "add":next(self.stream)node = nodes.Pos(self.parse_unary(False), lineno=lineno)else:node = self.parse_primary()node = self.parse_postfix(node)if with_filter:node = self.parse_filter_expr(node)return nodedef parse_primary(self):token = self.stream.currentif token.type == "name":if token.value in ("true", "false", "True", "False"):node = nodes.Const(token.value in ("true", "True"), lineno=token.lineno)elif token.value in ("none", "None"):node = nodes.Const(None, lineno=token.lineno)else:node = nodes.Name(token.value, "load", lineno=token.lineno)next(self.stream)elif token.type == "string":next(self.stream)buf = [token.value]lineno = token.linenowhile self.stream.current.type == "string":buf.append(self.stream.current.value)next(self.stream)node = nodes.Const("".join(buf), lineno=lineno)elif token.type in ("integer", "float"):next(self.stream)node = nodes.Const(token.value, lineno=token.lineno)elif token.type == "lparen":next(self.stream)node = self.parse_tuple(explicit_parentheses=True)self.stream.expect("rparen")elif token.type == "lbracket":node = self.parse_list()elif token.type == "lbrace":node = self.parse_dict()else:self.fail("unexpected '%s'" % describe_token(token), token.lineno)return nodedef parse_tuple(self,simplified=False,with_condexpr=True,extra_end_rules=None,explicit_parentheses=False,):"""Works like `parse_expression` but if multiple expressions aredelimited by a comma a :class:`~jinja2.nodes.Tuple` node is created.This method could also return a regular expression instead of a tupleif no commas where found.The default parsing mode is a full tuple. If `simplified` is `True`only names and literals are parsed. The `no_condexpr` parameter isforwarded to :meth:`parse_expression`.Because tuples do not require delimiters and may end in a bogus commaan extra hint is needed that marks the end of a tuple. For examplefor loops support tuples between `for` and `in`. In that case the`extra_end_rules` is set to ``['name:in']``.`explicit_parentheses` is true if the parsing was triggered by anexpression in parentheses. This is used to figure out if an emptytuple is a valid expression or not."""lineno = self.stream.current.linenoif simplified:parse = self.parse_primaryelif with_condexpr:parse = self.parse_expressionelse:def parse():return self.parse_expression(with_condexpr=False)args = []is_tuple = Falsewhile 1:if args:self.stream.expect("comma")if self.is_tuple_end(extra_end_rules):breakargs.append(parse())if self.stream.current.type == "comma":is_tuple = Trueelse:breaklineno = self.stream.current.linenoif not is_tuple:if args:return args[0]# if we don't have explicit parentheses, an empty tuple is# not a valid expression. This would mean nothing (literally# nothing) in the spot of an expression would be an empty# tuple.if not explicit_parentheses:self.fail("Expected an expression, got '%s'"% describe_token(self.stream.current))return nodes.Tuple(args, "load", lineno=lineno)def parse_list(self):token = self.stream.expect("lbracket")items = []while self.stream.current.type != "rbracket":if items:self.stream.expect("comma")if self.stream.current.type == "rbracket":breakitems.append(self.parse_expression())self.stream.expect("rbracket")return nodes.List(items, lineno=token.lineno)def parse_dict(self):token = self.stream.expect("lbrace")items = []while self.stream.current.type != "rbrace":if items:self.stream.expect("comma")if self.stream.current.type == "rbrace":breakkey = self.parse_expression()self.stream.expect("colon")value = self.parse_expression()items.append(nodes.Pair(key, value, lineno=key.lineno))self.stream.expect("rbrace")return nodes.Dict(items, lineno=token.lineno)def parse_postfix(self, node):while 1:token_type = self.stream.current.typeif token_type == "dot" or token_type == "lbracket":node = self.parse_subscript(node)# calls are valid both after postfix expressions (getattr# and getitem) as well as filters and testselif token_type == "lparen":node = self.parse_call(node)else:breakreturn nodedef parse_filter_expr(self, node):while 1:token_type = self.stream.current.typeif token_type == "pipe":node = self.parse_filter(node)elif token_type == "name" and self.stream.current.value == "is":node = self.parse_test(node)# calls are valid both after postfix expressions (getattr# and getitem) as well as filters and testselif token_type == "lparen":node = self.parse_call(node)else:breakreturn nodedef parse_subscript(self, node):token = next(self.stream)if token.type == "dot":attr_token = self.stream.currentnext(self.stream)if attr_token.type == "name":return nodes.Getattr(node, attr_token.value, "load", lineno=token.lineno)elif attr_token.type != "integer":self.fail("expected name or number", attr_token.lineno)arg = nodes.Const(attr_token.value, lineno=attr_token.lineno)return nodes.Getitem(node, arg, "load", lineno=token.lineno)if token.type == "lbracket":args = []while self.stream.current.type != "rbracket":if args:self.stream.expect("comma")args.append(self.parse_subscribed())self.stream.expect("rbracket")if len(args) == 1:arg = args[0]else:arg = nodes.Tuple(args, "load", lineno=token.lineno)return nodes.Getitem(node, arg, "load", lineno=token.lineno)self.fail("expected subscript expression", token.lineno)def parse_subscribed(self):lineno = self.stream.current.linenoif self.stream.current.type == "colon":next(self.stream)args = [None]else:node = self.parse_expression()if self.stream.current.type != "colon":return nodenext(self.stream)args = [node]if self.stream.current.type == "colon":args.append(None)elif self.stream.current.type not in ("rbracket", "comma"):args.append(self.parse_expression())else:args.append(None)if self.stream.current.type == "colon":next(self.stream)if self.stream.current.type not in ("rbracket", "comma"):args.append(self.parse_expression())else:args.append(None)else:args.append(None)return nodes.Slice(lineno=lineno, *args)def parse_call(self, node):token = self.stream.expect("lparen")args = []kwargs = []dyn_args = dyn_kwargs = Nonerequire_comma = Falsedef ensure(expr):if not expr:self.fail("invalid syntax for function call expression", token.lineno)while self.stream.current.type != "rparen":if require_comma:self.stream.expect("comma")# support for trailing commaif self.stream.current.type == "rparen":breakif self.stream.current.type == "mul":ensure(dyn_args is None and dyn_kwargs is None)next(self.stream)dyn_args = self.parse_expression()elif self.stream.current.type == "pow":ensure(dyn_kwargs is None)next(self.stream)dyn_kwargs = self.parse_expression()else:if (self.stream.current.type == "name"and self.stream.look().type == "assign"):# Parsing a kwargensure(dyn_kwargs is None)key = self.stream.current.valueself.stream.skip(2)value = self.parse_expression()kwargs.append(nodes.Keyword(key, value, lineno=value.lineno))else:# Parsing an argensure(dyn_args is None and dyn_kwargs is None and not kwargs)args.append(self.parse_expression())require_comma = Trueself.stream.expect("rparen")if node is None:return args, kwargs, dyn_args, dyn_kwargsreturn nodes.Call(node, args, kwargs, dyn_args, dyn_kwargs, lineno=token.lineno)def parse_filter(self, node, start_inline=False):while self.stream.current.type == "pipe" or start_inline:if not start_inline:next(self.stream)token = self.stream.expect("name")name = token.valuewhile self.stream.current.type == "dot":next(self.stream)name += "." + self.stream.expect("name").valueif self.stream.current.type == "lparen":args, kwargs, dyn_args, dyn_kwargs = self.parse_call(None)else:args = []kwargs = []dyn_args = dyn_kwargs = Nonenode = nodes.Filter(node, name, args, kwargs, dyn_args, dyn_kwargs, lineno=token.lineno)start_inline = Falsereturn nodedef parse_test(self, node):token = next(self.stream)if self.stream.current.test("name:not"):next(self.stream)negated = Trueelse:negated = Falsename = self.stream.expect("name").valuewhile self.stream.current.type == "dot":next(self.stream)name += "." + self.stream.expect("name").valuedyn_args = dyn_kwargs = Nonekwargs = []if self.stream.current.type == "lparen":args, kwargs, dyn_args, dyn_kwargs = self.parse_call(None)elif self.stream.current.type in ("name","string","integer","float","lparen","lbracket","lbrace",) and not self.stream.current.test_any("name:else", "name:or", "name:and"):if self.stream.current.test("name:is"):self.fail("You cannot chain multiple tests with is")arg_node = self.parse_primary()arg_node = self.parse_postfix(arg_node)args = [arg_node]else:args = []node = nodes.Test(node, name, args, kwargs, dyn_args, dyn_kwargs, lineno=token.lineno)if negated:node = nodes.Not(node, lineno=token.lineno)return nodedef subparse(self, end_tokens=None):body = []data_buffer = []add_data = data_buffer.appendif end_tokens is not None:self._end_token_stack.append(end_tokens)def flush_data():if data_buffer:lineno = data_buffer[0].linenobody.append(nodes.Output(data_buffer[:], lineno=lineno))del data_buffer[:]try:while self.stream:token = self.stream.currentif token.type == "data":if token.value:add_data(nodes.TemplateData(token.value, lineno=token.lineno))next(self.stream)elif token.type == "variable_begin":next(self.stream)add_data(self.parse_tuple(with_condexpr=True))self.stream.expect("variable_end")elif token.type == "block_begin":flush_data()next(self.stream)if end_tokens is not None and self.stream.current.test_any(*end_tokens):return bodyrv = self.parse_statement()if isinstance(rv, list):body.extend(rv)else:body.append(rv)self.stream.expect("block_end")else:raise AssertionError("internal parsing error")flush_data()finally:if end_tokens is not None:self._end_token_stack.pop()return bodydef parse(self):"""Parse the whole template into a `Template` node."""result = nodes.Template(self.subparse(), lineno=1)result.set_environment(self.environment)return result
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。