work on vizml
authorBen Pfaff <blp@cs.stanford.edu>
Sun, 26 Jan 2020 05:57:02 +0000 (05:57 +0000)
committerBen Pfaff <blp@cs.stanford.edu>
Sun, 26 Jan 2020 05:57:02 +0000 (05:57 +0000)
src/output/spv/spvxml-helpers.c
src/output/spv/vizml.grammar
src/output/spv/xml-parser-generator

index eb99ccc3a53f3050f507884a7f14e841cba5e72b..fb1420351d18bff4cb14ec9bf9eb23f9533e93ec 100644 (file)
@@ -27,6 +27,7 @@
 #include "libpspp/hash-functions.h"
 #include "libpspp/str.h"
 
+#include "gl/c-ctype.h"
 #include "gl/xvasprintf.h"
 
 char * WARN_UNUSED_RESULT
@@ -315,14 +316,31 @@ xmlGetPropNodeValueInternal(const xmlAttr *prop)
     return(NULL);
 }
 
+/* Returns true if S consists entirely of digits (and at least one digit) */
+static bool
+all_digits (const char *s)
+{
+  for (size_t i = 0; ; i++)
+    if (!c_isdigit (s[i]))
+      return i > 0 && (s[i] == '\0' || (s[i] == '_' && s[i + 1] == '\0'));
+}
+
 static struct spvxml_attribute *
 find_attribute (struct spvxml_node_context *nctx, const char *name)
 {
   /* XXX This is linear search but we could use binary search. */
   for (struct spvxml_attribute *a = nctx->attrs;
        a < &nctx->attrs[nctx->n_attrs]; a++)
-    if (!strcmp (a->name, name))
-      return a;
+    {
+      size_t a_len = strlen (a->name);
+      if (!strcmp (a->name, name)
+          || (a->name[a_len - 1] == '#'
+              && !strncmp (name, a->name, a_len - 1)
+              && all_digits (name + a_len - 1))
+          || (a->name[a_len - 1] == '%'
+              && !strncmp (name, a->name, a_len - 1)))
+        return a;
+    }
 
   return NULL;
 }
@@ -371,8 +389,13 @@ spvxml_parse_attributes (struct spvxml_node_context *nctx)
         }
       if (a->value)
         {
-          spvxml_attr_error (nctx, "Duplicate attribute \"%s\".", a->name);
-          return;
+          if (a->name[strlen (a->name) - 1] != '#'
+              && a->name[strlen (a->name) - 1] != '%')
+            {
+              spvxml_attr_error (nctx, "Duplicate attribute \"%s\".", a->name);
+              return;
+            }
+          free (a->value);
         }
       a->value = CHAR_CAST (char *, xmlGetPropNodeValueInternal (node));
     }
index 08fb88ea7495e009e2b11fa27ceca3e0f496624d..375a0955386de6854d46467dada4741d39d647d5 100644 (file)
@@ -25,15 +25,14 @@ visualization
    :version
    :schemaLocation?
    :clip?
-=> location*
-   visualization_extension?
-   userSource
-   (sourceVariable | derivedVariable | expressionVariable)+
-   categoricalDomain?
-   intervalDomain*
+=> (visualization_extension | location)*
+   userSource+
+   (sourceVariable | derivedVariable | expressionVariable
+    | intervalDomain | categoricalDomain)+
    graph
    labelFrame[lf1]*
-   container?
+   container*
+   legend?
    labelFrame[lf2]*
    style+
    layerController?
@@ -48,45 +47,56 @@ extension[visualization_extension]
    :legend1?
    :legend2?
    :styleCycleRepository?
-   :title1?
-   :title2?
+   :title#?
    :subtitle?
-   :footnote1?
-   :footnote2?
-   :footnote3?
-   :textbox1?
-   :textbox2?
-   :statisticsSummary?
-   :statisticsSummary1?
-   :dataSetHandle_0?
-   :dataSetName_0?
-   :dataSetHandle_1?
-   :dataSetName_1?
-   :dataSetHandle_2?
-   :dataSetName_2?
+   :footnote#?
+   :textbox#?
+   :statisticsSummary[statisticsSummary_]?
+   :statisticsSummary#?
+   :dataSetHandle_#?
+   :dataSetName_#?
    :percentDomain=(percentDomain)?
-   :regionStatistic_2270?
-   :regionStatistic_2737?
-   :smoothStatistic_2717?
+   :regionStatistic_#?
+   :smoothStatistic_%?
+   :originLineX?
    :originLineY?
    :boxplotLinkWidth=bool?
+   :line_%?
+   :preserveLocations=bool?
+   :creator?
+   :template?
+   :accelerator=bool?
+   :certified=bool?
 => EMPTY
 
 userSource :missing=(listwise | pairwise)? => EMPTY   // Related to omit_empty?
 
-categoricalDomain => variableReference categories? simpleSort?
+categoricalDomain
+   :date=bool?
+=> (categories | variableReference | domainReference)+ simpleSort?
+
+domainReference
+   :ref=ref categoricalDomain
+=> EMPTY
 
 intervalDomain
    :date=bool?
-=> ETC
+=> (variableReference | range)*
+
+range
+   :exact=bool?
+   :max=real
+   :min=real
+=> EMPTY
 
 categories
    :remove=bool?
 => ETC
 
 simpleSort
-   :method[sort_method]=(custom | natural)
-=> categoryOrder
+   :method[sort_method]=(custom | natural | percent)
+   :descending=bool?
+=> categoryOrder?
 
 sourceVariable
    :id
@@ -102,7 +112,9 @@ sourceVariable
    :weight=bool?
    :shortLabel?
    :description?
-=> variable_extension* (format | stringFormat)?
+   :inputDateFormatPattern?
+   :measurementLevel=(ratio)?
+=> variable_extension* (format | stringFormat | numberFormat)?
 
 derivedVariable
    :id
@@ -131,6 +143,7 @@ expressionVariable
    :expression?
    :shortLabel?
    :label?
+   :source?
 => ETC
 
 valueMapEntry :from :to => EMPTY
@@ -141,22 +154,24 @@ graph
    :cellStyle=ref style
    :style=ref style
    :axesOutside=bool?
-=> location+ coordinates faceting? point[p1]? facetLayout?
-   line[line1]? point[p2]? schema? interval* lineGuide? line[line2]?
-   functionGuide*
+=> (location | coordinates | faceting | point | facetLayout
+    | line | schema | interval | lineGuide | functionGuide | edge | path
+    | planeGuide | frameGuide)*
 
 location
    :part=(height | width | top | bottom | left | right)
    :method=(sizeToContent | attach | fixed | same)
    :min?
    :max?
-   :target=ref (labelFrame | graph | container)?
+   :target=ref (labelFrame | graph | container | legend)?
    :value?
    :specifiedByUser=bool?
 => EMPTY
 
 coordinates
-=> (dimension | transposeTransform | reflectionTransform | polarTransform)*
+=> (dimension | transposeTransform | reflectionTransform | polarTransform
+    | sameRatioTransform | insetTransform | rectangularTransform
+    | obliqueTransform)*
 
 dimension
    :domain?
@@ -167,7 +182,14 @@ dimension
    :unionDomain=bool?
    :clusterGap?
    :clusterVariable?
-=> ETC
+=> scale? axis*
+
+scale
+   :invert=bool
+   :method[scale_method] // linear | log.safe
+   :origin=real
+   :base=int?
+=> EMPTY
 
 transposeTransform => ETC
 
@@ -182,20 +204,49 @@ polarTransform
    :startAngle=real?
 => ETC
 
+sameRatioTransform
+   :center=bool
+=> EMPTY
+
+insetTransform
+   :x-min=real
+   :x-max=real
+   :y-min=real
+   :y-max=real
+=> EMPTY
+
+rectangularTransform
+   :distance=real
+   :phi=real
+   :theta=real
+   :zeta=real
+=> EMPTY
+
+obliqueTransform
+   :distance=real
+   :foreShortening=real
+   :theta=real
+   :xscale=real
+=> EMPTY
+
 faceting
    :method[faceting_method]=(nest | cross | dot)?
-=> layer[layers1]* cross layer[layers2]*
+=> layer[layers1]* cross layer[layers2]* | dot
 
 cross => (unity | nest | cross | variableReference)+
 
+dot => unity variableReference
+
 nest => variableReference[vars]+
 
 unity => EMPTY
 
-variableReference :ref=ref (sourceVariable | derivedVariable) => EMPTY
+variableReference
+   :ref=ref (sourceVariable | derivedVariable | expressionVariable)
+=> EMPTY
 
 layer
-   :variable=ref (sourceVariable | derivedVariable)
+   :variable=ref (sourceVariable | derivedVariable | expressionVariable)
    :value
    :visible=bool?
    :method[layer_method]=(nest)?
@@ -218,6 +269,7 @@ tableLayout
 facetLevel
    :level=int
    :gap?
+   :style=ref style?
 => axis?
 
 point
@@ -226,8 +278,15 @@ point
    :showCollidingLabels=bool?
    :labelCollisionHandling=(none | normal)?
    :style=ref style?
-   :positionModifier=(none | stack)?
+   :positionModifier=(none | stack | pile)?
    :zOrder=int?
+   :clip=(none)?
+=> styleBy? color? labeling* split? x y z?
+// labeling*3 x y z
+
+split
+   :split=bool
+   :variable=ref (sourceVariable | derivedVariable | expressionVariable)
 => ETC
 
 line
@@ -237,11 +296,29 @@ line
    :name?
    :style=ref style?
    :dot=bool?
-   :positionModifier=(none | stack)?
+   :positionModifier=(none | stack | pile)?
    :showDiscontinuity=bool?
    :zOrder=int?
    :coordinates?
-=> ETC
+=> summaryStatistic? smoothStatistic? styleBy? regionStatistic? distributionStatistic? color? x y?
+
+smoothStatistic
+   :method[smoothStatistic_method]=(step-left | linear)
+   :bounded=bool?
+   :degree=int?
+   :kernel=(uniform)?
+=> EMPTY
+
+regionStatistic
+   :alpha=real
+   :degree=int
+   :predictIndividuals=bool
+   :regionType=(linear)
+=> EMPTY
+
+distributionStatistic
+   :distribution=(normal)
+=> EMPTY
 
 lineGuide
    :style=ref style?
@@ -254,8 +331,48 @@ functionGuide
    :segments=int?
    :style=ref style?
    :value?
+   :zOrder=int?
 => ETC
 
+edge
+   :dot=bool
+   :positionModifier=(none | stack | pile)
+   :style=ref style
+=> attach? position_element? linkStatistic? x? y? z?
+
+linkStatistic
+   :y=(base)
+=> EMPTY
+
+path
+   :breakRepresentation=(wings)
+   :dot=bool
+   :labelCollisionHandling=(none | normal)
+   :name
+   :showCollidingLabels=bool
+   :style=ref style
+=> styleBy x y
+
+planeGuide
+   :normal=(x | y | z)
+   :style=ref style
+=> EMPTY
+
+frameGuide
+   :complete=bool?
+   :style=ref style
+   :zOrder=int?
+=> EMPTY
+
+attach
+   :target=(point)
+   :targetKey=ref (sourceVariable | derivedVariable | expressionVariable)
+=> EMPTY
+
+position[position_element]
+   :variable=ref expressionVariable
+=> EMPTY
+
 schema
    :dot=bool?
    :extremeStyle?
@@ -267,7 +384,7 @@ schema
    :showCollidingLabels=bool?
    :labelCollisionHandling=(none | normal)?
    :style=ref style?
-   :positionModifier=(none | stack)?
+   :positionModifier=(none | stack | pile)?
    :zOrder=int?
 => ETC
 
@@ -275,22 +392,26 @@ axis
    :style=ref style
    :opposite=bool?
    :repeat=(never)?
-=> label? majorTicks
+   :combineAxisLabels=bool?
+   :maxSize=dimension?
+   :zOrder=int?
+   :transform=(linear)?
+=> label? majorTicks minorTicks? axisTransformParameter*
 
 label
    :style=ref style
    :textFrameStyle=ref style?
-   :purpose=(title | subTitle | subSubTitle | layer | footnote)?
+   :purpose=(title | subTitle | subSubTitle | layer | footnote | auto)?
 => text+ | descriptionGroup
 
 descriptionGroup
-   :target=ref (faceting | interval)
+   :target=ref (faceting | interval | sourceVariable | line)
    :separator?
 => (description | text)+
 
 description
    :name?
-=> format*
+=> (format | numberFormat)*
 
 majorTicks
    :labelAngle=int
@@ -300,14 +421,27 @@ majorTicks
    :labelFrequency=int?
    :stagger=bool?
    :markStyle=ref style?
-   :base=int?
-=> gridline?
+   :base=real?
+   :delta=int?
+   :position[majorTicks_position]=(outside)?
+=> gridline? format?
+
+minorTicks
+   :markStyle=ref style
+   :number=int
+   :position[minorTicks_position]=(outside)?
+=> gridline? format?
 
 gridline
    :style=ref style
    :zOrder=int
 => EMPTY
 
+axisTransformParameter
+   :parameterName=(base-ratio | base-match | derived-ratio | derived-match)
+   :parameterValue=real
+=> EMPTY
+
 setCellProperties
    :applyToConverse=bool?
 => (setStyle | setFrameStyle | setFormat | setMetaData)* union[union_]?
@@ -362,7 +496,7 @@ format
    :showMillis=bool?
    :dayType=(month | year)?
    :hourFormat=(AMPM | AS_24 | AS_12)?
-   :minimumIntegerDigits=int?
+   :minimumIntegerDigits?
    :maximumFractionDigits=int?
    :minimumFractionDigits?
    :useGrouping=bool?
@@ -373,6 +507,7 @@ format
    :tryStringsAsNumbers=bool?
    :negativesOutside=bool?
    :hiddenseparator=bool?
+   :scalingFactor=real?
 => relabel* affix*
 
 numberFormat
@@ -384,6 +519,7 @@ numberFormat
    :small=real?
    :prefix?
    :suffix?
+   :hiddenseparator=bool?
 => affix*
 
 stringFormat => relabel* affix*
@@ -449,13 +585,13 @@ union => intersect+
 intersect => where+ | intersectWhere | alternating | EMPTY
 
 where
-   :variable=ref (sourceVariable | derivedVariable)
+   :variable=ref (sourceVariable | derivedVariable | expressionVariable)
    :include
 => EMPTY
 
 intersectWhere
-   :variable=ref (sourceVariable | derivedVariable)
-   :variable2=ref (sourceVariable | derivedVariable)
+   :variable=ref (sourceVariable | derivedVariable | expressionVariable)
+   :variable2=ref (sourceVariable | derivedVariable | expressionVariable)
 => EMPTY
 
 alternating => EMPTY
@@ -473,41 +609,75 @@ interval
    :labelCollisionHandling=(none | normal)?
    :showCollidingLabels=bool?
    :name?
-   :positionModifier=(none | stack)?
+   :positionModifier=(none | stack | pile)?
    :zOrder=int?
-=> binStatistic? summaryStatistic? styleBy? color? x? y? labeling? footnotes?
+   :zLocation=real?
+=> binStatistic? summaryStatistic* (styleBy | labeling | color | offset)* x? y? footnotes?
+// styleBy                  labeling         x
+//                          labeling styleBy x y
+// summaryStatistic   color labeling         x
+// styleBy                  labeling offset  x
+// summaryStatistic*2 color labeling
 
 labeling
    :style=ref style?
-   :variable=ref (sourceVariable | derivedVariable)
+   :variable=ref (sourceVariable | derivedVariable | expressionVariable)
    :connectingLines=bool?
    :textFrameStyle=ref style?
-=> (formatting | format | footnotes)*
+   :clamp=bool?
+=> (formatting | format | summaryStatistic | numberFormat | transparency | footnotes | labelLocation)*
+
+offset
+   :domain=ref categoricalDomain
+   :high
+   :low
+   :variable=ref (sourceVariable | derivedVariable | expressionVariable)
+=> EMPTY
 
-formatting :variable=ref (sourceVariable | derivedVariable) => formatMapping*
+transparency
+   :affect=(both | main)
+   :domain=ref intervalDomain
+   :low=real
+   :high=real
+   :variable=ref (sourceVariable | derivedVariable | expressionVariable)
+=> EMPTY
+
+labelLocation
+   :domain=ref intervalDomain
+   :role=(x | y)
+   :variable=ref (sourceVariable | derivedVariable | expressionVariable)
+=> EMPTY
+
+formatting :variable=ref (sourceVariable | derivedVariable | expressionVariable) => formatMapping*
 
 formatMapping :from=int => format?
 
 color
-   :affect=(both)?
+   :affect=(both | main)?
    :cycle=ref styleCycle?
    :variable?
    :clamp=bool?
+   :dimension=int?
+   :domain=ref categoricalDomain?
 => ETC
 
 x
-   :variable?
-=> ETC
+   :variable=ref (sourceVariable | derivedVariable | expressionVariable)
+=> EMPTY
 
 y
-   :variable?
-=> ETC
+   :variable=ref (sourceVariable | derivedVariable | expressionVariable)
+=> EMPTY
+
+z
+   :variable=ref (sourceVariable | derivedVariable | expressionVariable)
+=> EMPTY
 
 styleBy
    :styleCycle?
    :variable?
    :dimension=int?
-=> ETC
+=> EMPTY
 
 binStatistic
    :gridType=(square)?
@@ -516,18 +686,35 @@ binStatistic
 => ETC
 
 summaryStatistic
-   :method[summaryStatistic_method]=(count | range)?
+   :method[summaryStatistic_method]=(count | range | percent | sum | mean | percentCount)?
    :symbol[summaryStatistic_symbol]=(circle | square)?
    :summaryBase=(coordinate)?
    :convertIntervalToSingleValue=bool?
+   :summaryIndex=int?
+   :augment=(missing)?
 => ETC
 
 footnotes
    :superscript=bool?
-   :variable=ref (sourceVariable | derivedVariable)
+   :variable=ref (sourceVariable | derivedVariable | expressionVariable)
 => footnoteMapping*
 
-footnoteMapping :definesReference=int :from=int :to => EMPTY
+footnoteMapping
+   :definesReference=int
+   :from=int
+   :to
+=> EMPTY
+
+legend
+   :style=ref style
+   :tickFrameStyle=ref style
+   :tickTextStyle=ref style
+=> location+ label? legendTarget+
+
+legendTarget
+   :target=ref (styleBy | color | line | point)
+   :domain=ref categoricalDomain?
+=> EMPTY
 
 style
    :color=color?
@@ -563,9 +750,21 @@ style
    :stroke-dasharray?
    :text-fit=bool?
    :depth?
-   :symbol=(circle | ibeam | flower)?
+   :symbol=(circle | ibeam | flower | square | line | acrossDown | cross
+            | polygon | plus | bowtie | star | arrow | none)?
    :margin=dimension?
    :padding=dimension?
+   :padding-left=dimension?
+   :padding-right=dimension?
+   :padding-top=dimension?
+   :padding-bottom=dimension?
+   :maxAttachedTextHeight=dimension?
+   :opacity=int?
+   :opacity2=int?
+   :labelInside=(preferred | false)?
+   :glyph-sides=int?
+   :glyph-angle=real?
+   :glyph-aspect=real?
 => style*
 
 layerController
@@ -580,15 +779,22 @@ container
 
 extension[container_extension] :combinedFootnotes=(true) => EMPTY
 
-labelFrame :style=ref style => location+ label? paragraph?
+labelFrame
+   :style=ref style
+=> labelFrame_extension* location+ label? paragraph?
 
-legend
-   :style=ref style?
-   :tickFrameStyle=ref style?
-   :tickTextStyle=ref style?
-=> ETC
+extension[labelFrame_extension]
+   :graphId=ref graph
+   :position[labelFrame_position]=(title | footnote)
+=> EMPTY
 
 paragraph :hangingIndent=dimension? => EMPTY
 
-styleCycle
-=> ETC
+styleCycle => cycle+ styleCycle_extension*
+
+cycle => style+
+
+extension[styleCycle_extension]
+   :aesthetic=(color)?
+   :immutables=int?
+=> EMPTY
index 546ca97d00f2d76a512cccf2447a209a6050ebd5..df34fbf8fffb41b8de9bbe1afa9c22a79d0ca505 100644 (file)
@@ -70,7 +70,7 @@ def match_id(id_):
 
 
 def is_idchar(c):
-    return c.isalnum() or c in '-_'
+    return c.isalnum() or c in '-_#%'
 
 
 def get_token():
@@ -796,9 +796,13 @@ void
     print('}')
 
 def name_to_id(s):
-    return s[0].lower() + ''.join(['_%c' % x.lower() if x.isupper() else x
-                                   for x in s[1:]]).replace('-', '_')
-
+    return ((s[0].lower()
+             + ''.join(['_%c' % x.lower() if x.isupper() else x
+                        for x in s[1:]]).replace('-', '_'))
+            .replace("_#", "")
+            .replace("#", "")
+            .replace("_%", "")
+            .replace("%", ""))
 
 def print_recurse_members(attributes, rhs, function):
     if rhs['type'] == 'etc' or rhs['type'] == 'empty':
@@ -931,11 +935,6 @@ void
     print('}')
 
 
-def name_to_id(s):
-    return s[0].lower() + ''.join(['_%c' % x.lower() if x.isupper() else x
-                                   for x in s[1:]]).replace('-', '_')
-
-
 if __name__ == "__main__":
     argv0 = sys.argv[0]
     try:
@@ -1090,3 +1089,4 @@ struct %(prefix)s%(name)s *%(prefix)scast_%(name)s (const struct spvxml_node *);
 #endif /* %(PREFIX)sPARSER_H */""" % {'PREFIX': prefix.upper()})
     else:
         sys.stderr.write("%s: bad usage (use --help for help)" % argv0)
+