aceontech / MarkupKitLint

Linting and validation utilities for MarkupKit integrated into Xcode.
MIT License
0 stars 0 forks source link

XML schema validation #1

Open aceontech opened 7 years ago

aceontech commented 7 years ago

Create or generate a basic schema which can be used to validate MarkupKit XML files. It should have knowledge of all the standard components and attributes, while still allowing custom (unknown) types.

aceontech commented 7 years ago

A sample of what it could look like can be found here: https://github.com/jarrroo/MarkupKitLint/blob/eaa4b026e0d9adbe5027771185808cc2cb0b8cd9/Schemas/MarkupKitBasic.xsd

It was generated based on all of MarkupKit's sample XML content. Clearly just a starting point as it would need some generalization & tweaking.

gk-brown commented 7 years ago

I looked into something like this myself a while back. My plan was to generate an XSD file by using the Objective-C runtime APIs to enumerate all of the classes that extend UIView along with their properties :

https://developer.apple.com/documentation/objectivec/objective_c_runtime?language=objc

I was originally hoping to do this in a Mac command-line tool, but it doesn't seem like it's possible to load iOS simulator binaries in macOS, even though they are compiled for x86.

However, it should be possible to do in iOS. It's a little clunky, but the generated document could probably be retrieved by navigating to the Documents directory for the simulator.

aceontech commented 7 years ago

Ah yes, I remember looking into this briefly too. I'd prefer this approach, as opposed to cobbling together the XSD by hand 🤔. Thanks for the pointer.

gk-brown commented 7 years ago

It's been a while since I worked with XML Schema, but I believe it is possible to define hierarchical relationships. So there could be definitions for UIView, UIControl, UIButton, etc. Might be worth considering.

Although I can see how it would be useful, I hadn't considered restricting sub-elements to specific types (such as LMTableView/LMTableViewCell). My approach was going to be a bit simpler - it would basically have validated an element's attributes (i.e. properties), but not its content.

I think it should be pretty easy to dynamically generate a schema that can do this (plus the reserved attributes like "id", "style", "class", and "on..."). However, it will be more difficult to automatically infer valid sub-elements. In fact, it may not even be possible to know in advance the complete set of valid elements, since an application may define custom view types or make use of additional 3rd-party frameworks. Further, some existing classes support untyped sub-elements like <sectionHeader> and <segment>.

Similarly, it may be difficult or impossible to generate a schema that can validate attribute values. The Objective-C runtime knows about property names, but not much about property types. For example, enum types are not available at runtime - they are reported as int/long, I think. Since enum values are specified as strings in MarkupKit, we wouldn't want to restrict the corresponding attribute types to numerics.

Finally, I don't think XSD handles processing instructions. So we wouldn't be able to validate things like <?sectionBreak?>.

So, although I didn't get too far along with it, my approach was simply going to validate property names for known element types. Ideally, unknown elements would be ignored rather than flagged as errors. Not sure if this is possible with xmllint or not.

aceontech commented 7 years ago

I believe it is possible to define hierarchical relationships

It's been ages for me too, but I think even class inheritance can be expressed.

I hadn't considered restricting sub-elements to specific types (such as LMTableView/LMTableViewCell)

My example XSD was generated by the Xmplify app, which attempts to infer these types of relationships automatically. If used at all, I'd only restrict a very strict subset (like the pair you mentioned).

it may be difficult or impossible to generate a schema that can validate attribute values. The Objective-C runtime knows about property names, but not much about property types.

Perhaps it would be enough to just validate the mere presence/applicability of attributes, and omit value validation altogether at first.

unknown elements would be ignored rather than flagged as errors

I think this can be achieved with <xs:any> wildcards.

I'll play around a bit, could be a dead end, but it's worth a shot, I think.

gk-brown commented 7 years ago

It's been ages for me too, but I think even class inheritance can be expressed.

Yeah, that's exactly what I meant. You could define a an element type hierarchy that mirrors the UIView class hierarchy.

Perhaps it would be enough to just validate the mere presence/applicability of attributes, and omit value validation altogether at first.

Agreed - that's exactly what I was thinking.

I think this can be achieved with wildcards.

Based on some limited research, I think you are right.

I'll play around a bit, could be a dead end, but it's worth a shot, I think.

It's definitely worth a shot (especially if you are volunteering to do it)! ;-) It's something I would definitely use, and I imagine a lot of other developers would find it useful as well.

aceontech commented 7 years ago

if you are volunteering to do it

Yes! MarkupKit need this kind of tooling for it to become mainstream 👍 .

aceontech commented 7 years ago

Made some initial headway here!

I looked into how <xs: any> actually works, but I got stuck right. It turns out XSDs do not support "nameless" elements in the way I thought they did. Remember, we need this to allow unknown markup elements to pass through the validator, while still type- & attribute- checking MarkupKit's default set of markup elements.

I ran across an alternate XML schema language called RelaxNG, which is also supported by xmllint. RelaxNG has a more.. relaxed.. syntax and has way better support for "anyName" elements and attributes. I gave it a shot, and as far as I can see, I was able to make it work.

The resulting POC can be found in the Schemas/ directory. It's quite bare, but it achieves the following validation:

(1) LMSpacer (and any other we want to define) gets validated, if used. In this instance, it's verified to be an empty element, and fails if it has text content, e.g. <LMSpacer>text</LMSpacer>.

(2) The root element is allowed to have any name, which supports our 2 main use cases:

(3) The root element is allowed to have any attribute for now.

Other, undefined elements and attributes also pass through, allowing for user-defined markup elements.

Albeit limited, this POC proves RelaxNG schemas are flexible enough for our use case. Next, I will take a jab at building this schema dynamically using NSXML in an iOS app with MarkupKit loaded.

gk-brown commented 7 years ago

Nice work! I had forgotten about RelaxNG.

Just curious - do you know if it supports type hierarchies? If not, maybe something even simpler like DTD could be an option. On the other hand, I'm not sure how flexible DTD would be with wildcard types. Just something to think about.

aceontech commented 7 years ago

do you know if it supports type hierarchies?

No, RelaxNG doesn't support inheritance per se. However, as I understand it, element types' properties can be composed (a bit like mixins [my interpretation]). For instance, I think it should be possible to create <define> blocks for various common behaviors, and compose them into concrete types. The <define> declaration has a combine attribute for this purpose (interleave or choice). For instance, consider the following defines (probably pseudo XML, haven't verified yet):

<define name="LMBoxView" combine="interleave">
  <zeroOrMore>
    <attribute name="spacing"/>
  </zeroOrMore>
</define>

<define name="LMRowView" combine="interleave">
  <ref name="LMBoxView"/>

  <zeroOrMore>
    <attribute name="alignToBaseline"/>
  </zeroOrMore>
</define>

<define name="LMColumnView" combine="interleave">
  <ref name="LMBoxView"/>

  <zeroOrMore>
    <attribute name="alignToGrid"/>
  </zeroOrMore>
</define>

It's probably a little rough, but if it holds true, this mechanism could be used to compose the various types in MarkupKit without needing true inheritance.

DTD could be an option

AFAIK DTD doesn't support the "anyName" scenario like RelaxNG does. It has an ANY content type, which allows an element's content to be arbitrary, but from what I can see every element needs to be named in DTD.

gk-brown commented 7 years ago

Makes sense.

I just looked into DTD a bit - definitely no support for inheritance there, so there would be some attribute duplication. However, it would probably be primarily limited to UIView and UIControl, plus UIScrollView.

DTD's lack of support for undefined elements is definitely a disadvantage, but if it were possible to easily generate a DTD for a given framework (or app bundle), then it might not be such a big deal. It could be viewed as analogous to importing a module - if you don't import the module, your code won't compile.

For that matter, the same argument could be applied to XSDs. And we know that XSDs do support inheritance. But again, just thinking out loud...not really sure what the best approach would be.

gk-brown commented 7 years ago

Just a thought - using the Objective-C runtime, it should be possible to dynamically determine all public UIView subtypes exposed by a given bundle. However, it is not possible to infer which processing instructions or "untyped elements" (e.g. <segment>) are supported by a given view type. This could potentially be resolved by defining class methods that return PI and untyped element descriptors so this information could be determined at runtime.

aceontech commented 7 years ago

Great input! In any case, I'll get started on the runtime component, so we have an idea of what it takes to extract the model we need. Interesting stuff 🙂.

aceontech commented 7 years ago

Tweaked the schema some more. It should now allow a <root> element with arbitrary attributes, while still type-checking MarkupKit elements inside.

Also, I have the basic schema generation code, using the ObjC runtime. I just need to clean it up a bit so I can push it. I was able to extract all UIKit properties (down to UIView/UIControl) and emulate inheritance in the schema using <ref>.

gk-brown commented 7 years ago

Sounds good - can't wait to see it.

aceontech commented 7 years ago

I was able to push the first iteration of the schema generation code. It can be found in the /Tools directory. Here's a quick overview of its current capabilities.

It generates a RelaxNG schema that:

Declares the various MarkupKit element types along with their legal attribute names

Container elements (e.g. LMBoxView descendants) can contain

Allows for a <root> element, containing either

Current limitations

Attribute validation

Unknown elements

Next tasks

In order to achieve a schema that would be generally useful, without needing to resort to app-specific schema generation, I think the following things still need to be done

Attribute validation

Unknown elements

Support more MarkupKit elements

Set up more elaborate test cases

Sample schema

Here's a printout of the schema current generated by mkgen. This can also be found in the /Schemas directory

<?xml version="1.0" encoding="UTF-8"?>
<grammar xmlns="http://relaxng.org/ns/structure/1.0">

  <!-- Root of the document. -->
  <start>
    <ref name="RootElement"/>
  </start>

  <!--
        The root element can only be one of the known MarkupKit elements,
        or a <root> element.
  -->
  <define name="RootElement">
    <choice>
      <!-- [GENERATED] References to all known MarkupKit element types. -->
      <ref name="LMSpacer"/>

      <ref name="LMLinearGradientView"/>

      <ref name="LMRadialGradientView"/>

      <ref name="LMPlayerView"/>

      <ref name="LMColumnView"/>

      <ref name="LMRowView"/>

      <ref name="LMLayerView"/>

      <ref name="LMAnchorView"/>

      <ref name="LMScrollView"/>

      <ref name="LMPageView"/>

      <ref name="LMPickerView"/>

      <!--
        The <root> element can have any attribute (by necessity, as
        the actual type is only knowable at runtime). Its children
        must be MarkupKitElements.
      -->
      <element name="root">
        <zeroOrMore>
          <ref name="any_attribute"/>
        </zeroOrMore>

        <zeroOrMore>
          <ref name="MarkupKitElements"/>
        </zeroOrMore>
      </element>
    </choice>
  </define>

  <!--
    //////////////////////////////////////////////////////////////////////
    // MarkupKit Types                                                  //
    //////////////////////////////////////////////////////////////////////
    -->

  <!-- References all known MarkupKit element types.-->
  <define name="MarkupKitElements">
    <!-- [GENERATED] -->
    <zeroOrMore>
      <ref name="LMSpacer"/>
    </zeroOrMore>

    <zeroOrMore>
      <ref name="LMLinearGradientView"/>
    </zeroOrMore>

    <zeroOrMore>
      <ref name="LMRadialGradientView"/>
    </zeroOrMore>

    <zeroOrMore>
      <ref name="LMPlayerView"/>
    </zeroOrMore>

    <zeroOrMore>
      <ref name="LMColumnView"/>
    </zeroOrMore>

    <zeroOrMore>
      <ref name="LMRowView"/>
    </zeroOrMore>

    <zeroOrMore>
      <ref name="LMLayerView"/>
    </zeroOrMore>

    <zeroOrMore>
      <ref name="LMAnchorView"/>
    </zeroOrMore>

    <zeroOrMore>
      <ref name="LMScrollView"/>
    </zeroOrMore>

    <zeroOrMore>
      <ref name="LMPageView"/>
    </zeroOrMore>

    <zeroOrMore>
      <ref name="LMPickerView"/>
    </zeroOrMore>

    <zeroOrMore>
      <ref name="any_element"/>
    </zeroOrMore>
  </define>

  <!-- [GENERATED] -->
  <define name="LMSpacer">
    <element name="LMSpacer">

      <ref name="UIView"/>

      <empty/>

    </element>
  </define>

  <define name="LMLinearGradientView">
    <element name="LMLinearGradientView">

      <zeroOrMore>
        <attribute name="startX"/>
      </zeroOrMore>

      <zeroOrMore>
        <attribute name="startY"/>
      </zeroOrMore>

      <zeroOrMore>
        <attribute name="startPoint"/>
      </zeroOrMore>

      <zeroOrMore>
        <attribute name="endX"/>
      </zeroOrMore>

      <zeroOrMore>
        <attribute name="endY"/>
      </zeroOrMore>

      <zeroOrMore>
        <attribute name="endPoint"/>
      </zeroOrMore>

      <ref name="LMGradientView"/>

      <ref name="UIView"/>

      <empty/>

    </element>
  </define>

  <define name="LMRadialGradientView">
    <element name="LMRadialGradientView">

      <zeroOrMore>
        <attribute name="centerX"/>
      </zeroOrMore>

      <zeroOrMore>
        <attribute name="centerY"/>
      </zeroOrMore>

      <zeroOrMore>
        <attribute name="centerPoint"/>
      </zeroOrMore>

      <zeroOrMore>
        <attribute name="radius"/>
      </zeroOrMore>

      <ref name="LMGradientView"/>

      <ref name="UIView"/>

      <empty/>

    </element>
  </define>

  <define name="LMPlayerView">
    <element name="LMPlayerView">

      <ref name="UIView"/>

      <empty/>

    </element>
  </define>

  <define name="LMColumnView">
    <element name="LMColumnView">

      <zeroOrMore>
        <attribute name="alignToGrid"/>
      </zeroOrMore>

      <ref name="LMBoxView"/>

      <ref name="LMLayoutView"/>

      <ref name="UIView"/>

      <zeroOrMore>
        <ref name="MarkupKitElements"/>
      </zeroOrMore>

    </element>
  </define>

  <define name="LMRowView">
    <element name="LMRowView">

      <zeroOrMore>
        <attribute name="alignToBaseline"/>
      </zeroOrMore>

      <zeroOrMore>
        <attribute name="baseline"/>
      </zeroOrMore>

      <ref name="LMBoxView"/>

      <ref name="LMLayoutView"/>

      <ref name="UIView"/>

      <zeroOrMore>
        <ref name="MarkupKitElements"/>
      </zeroOrMore>

    </element>
  </define>

  <define name="LMLayerView">
    <element name="LMLayerView">

      <ref name="LMLayoutView"/>

      <ref name="UIView"/>

      <zeroOrMore>
        <ref name="MarkupKitElements"/>
      </zeroOrMore>

    </element>
  </define>

  <define name="LMAnchorView">
    <element name="LMAnchorView">

      <ref name="LMLayoutView"/>

      <ref name="UIView"/>

      <zeroOrMore>
        <ref name="MarkupKitElements"/>
      </zeroOrMore>

    </element>
  </define>

  <define name="LMScrollView">
    <element name="LMScrollView">

      <zeroOrMore>
        <attribute name="contentView"/>
      </zeroOrMore>

      <zeroOrMore>
        <attribute name="fitToWidth"/>
      </zeroOrMore>

      <zeroOrMore>
        <attribute name="fitToHeight"/>
      </zeroOrMore>

      <ref name="UIScrollView"/>

      <ref name="UIView"/>

      <zeroOrMore>
        <ref name="MarkupKitElements"/>
      </zeroOrMore>

    </element>
  </define>

  <define name="LMPageView">
    <element name="LMPageView">

      <zeroOrMore>
        <attribute name="pages"/>
      </zeroOrMore>

      <ref name="UIScrollView"/>

      <ref name="UIView"/>

      <zeroOrMore>
        <ref name="MarkupKitElements"/>
      </zeroOrMore>

    </element>
  </define>

  <define name="LMPickerView">
    <element name="LMPickerView">

      <ref name="UIPickerView"/>

      <ref name="UIView"/>

      <zeroOrMore>
        <ref name="MarkupKitElements"/>
      </zeroOrMore>

    </element>
  </define>

  <!--
  //////////////////////////////////////////////////////////////////////
  // UIKit Traits                                                     //
  //////////////////////////////////////////////////////////////////////
  -->

  <!-- [GENERATED] -->
  <define name="UIView">

    <zeroOrMore>
      <attribute name="width"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="minimumWidth"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="maximumWidth"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="height"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="minimumHeight"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="maximumHeight"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="aspectRatio"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="weight"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="anchor"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="horizontalContentCompressionResistancePriority"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="horizontalContentHuggingPriority"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="verticalContentCompressionResistancePriority"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="verticalContentHuggingPriority"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="layoutMarginTop"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="layoutMarginLeft"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="layoutMarginBottom"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="layoutMarginRight"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="skipsSubviewEnumeration"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="viewTraversalMark"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="viewDelegate"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="monitorsSubtree"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="backgroundColorSystemColorName"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="currentScreenScale"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="maskView"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="hash"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="superclass"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="description"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="debugDescription"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="gesturesEnabled"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="deliversTouchesForGesturesToSuperview"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="deliversButtonsForGesturesToSuperview"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="leadingAnchor"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="trailingAnchor"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="leftAnchor"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="rightAnchor"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="topAnchor"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="bottomAnchor"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="widthAnchor"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="heightAnchor"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="centerXAnchor"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="centerYAnchor"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="firstBaselineAnchor"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="lastBaselineAnchor"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="contentSizeNotificationToken"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="layoutMarginsGuide"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="readableContentGuide"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="previewingSegueTemplateStorage"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="interactionTintColor"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="wantsDeepColorDrawing"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="userInteractionEnabled"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="tag"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="layer"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="canBecomeFocused"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="focused"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="semanticContentAttribute"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="effectiveUserInterfaceLayoutDirection"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="traitCollection"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="preferredFocusEnvironments"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="preferredFocusedView"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="preferredFocusMovementStyle"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="linearFocusMovementSequences"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="focusedView"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="center"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="bounds"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="transform"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="collisionBoundsType"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="collisionBoundingPath"/>
    </zeroOrMore>

  </define>

  <define name="LMGradientView">

    <zeroOrMore>
      <attribute name="colors"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="locations"/>
    </zeroOrMore>

  </define>

  <define name="LMBoxView">

    <zeroOrMore>
      <attribute name="horizontalAlignment"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="verticalAlignment"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="spacing"/>
    </zeroOrMore>

  </define>

  <define name="LMLayoutView">

    <zeroOrMore>
      <attribute name="arrangedSubviews"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="layoutMarginsRelativeArrangement"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="topSpacing"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="bottomSpacing"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="leadingSpacing"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="trailingSpacing"/>
    </zeroOrMore>

  </define>

  <define name="UIScrollView">

    <zeroOrMore>
      <attribute name="contentInsetTop"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="contentInsetLeft"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="contentInsetBottom"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="contentInsetRight"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="currentPage"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="adjustsTargetsOnContentOffsetChanges"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="firstResponderKeyboardAvoidanceEnabled"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="programmaticScrollEnabled"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="scrollingToTop"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="isAnimatingScroll"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="isVerticalBouncing"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="isHorizontalBouncing"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="isAnimatingZoom"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="zoomAnchorPoint"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="scrollTestParameters"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="minimumContentOffset"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="maximumContentOffset"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="forwardsTouchesUpResponderChain"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="focusFastScrolling"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="attemptingToEndFocusFastScrolling"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="showingFocusFastScrollingPreview"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="contentOffset"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="contentSize"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="contentInset"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="directionalLockEnabled"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="bounces"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="alwaysBounceVertical"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="alwaysBounceHorizontal"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="pagingEnabled"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="scrollEnabled"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="showsHorizontalScrollIndicator"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="showsVerticalScrollIndicator"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="scrollIndicatorInsets"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="indicatorStyle"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="decelerationRate"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="indexDisplayMode"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="tracking"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="dragging"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="decelerating"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="delaysContentTouches"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="canCancelContentTouches"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="minimumZoomScale"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="maximumZoomScale"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="zoomScale"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="bouncesZoom"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="zooming"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="zoomBouncing"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="scrollsToTop"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="panGestureRecognizer"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="pinchGestureRecognizer"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="directionalPressGestureRecognizer"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="keyboardDismissMode"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="refreshControl"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="touchDelayForScrollDetection"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="scrollHysteresis"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="canScrollX"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="canScrollY"/>
    </zeroOrMore>

  </define>

  <define name="UIPickerView">

    <zeroOrMore>
      <attribute name="magnifierLineColor"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="enabled"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="retargetBehavior"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="usesModernStyle"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="highlightColor"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="textColor"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="textShadowColor"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="showsSelectionIndicator"/>
    </zeroOrMore>

    <zeroOrMore>
      <attribute name="numberOfComponents"/>
    </zeroOrMore>

  </define>

  <!--
  //////////////////////////////////////////////////////////////////////
  // Wildcard types                                                   //
  //////////////////////////////////////////////////////////////////////
  -->

  <!--Any individual element. -->
  <define name="any_element">
    <element>
      <anyName>
        <!--
          References all known MarkupKit element type names here.
          This mechanism allows unknown types to be used, while still
          type checking MarkupKit elements.
        -->
        <except>

          <!-- [GENERATED] -->
          <name>LMSpacer</name>

          <name>LMLinearGradientView</name>

          <name>LMRadialGradientView</name>

          <name>LMPlayerView</name>

          <name>LMColumnView</name>

          <name>LMRowView</name>

          <name>LMLayerView</name>

          <name>LMAnchorView</name>

          <name>LMScrollView</name>

          <name>LMPageView</name>

          <name>LMPickerView</name>

        </except>
      </anyName>

      <zeroOrMore>
        <ref name="any_attribute"/>
      </zeroOrMore>

      <zeroOrMore>
        <ref name="any_content"/>
      </zeroOrMore>
    </element>
  </define>

  <!-- Any contiguous collection of elements. -->
  <define name="any_content">
    <interleave>
      <zeroOrMore>
        <ref name="any_element"/>
      </zeroOrMore>
      <text/>
    </interleave>
  </define>

  <!-- Any attribute. -->
  <define name="any_attribute">
    <attribute>
      <anyName/>
    </attribute>
  </define>

</grammar>
gk-brown commented 7 years ago

Do actual attribute value validation based on the introspected property type. This information is available through the runtime, so it's just a matter of setting up the correct RNG rules for each type.

As I recall, the runtime provides only very limited type information (basically just numeric primitives - no classes or enumerated types). That's why I was suggesting that we might only be able to validate attribute names, vs. both names and values. But I could be wrong.

Make [unknown elements] inherit the UIView trait so at least basic attribute validation can be done, while allowing unknown attributes through.

That's an interesting idea, but how would it work for untyped elements such as <segment>?

I agree that app-specific schema generation would not be ideal. I'd much rather include a complete schema with each MarkupKit release, if possible. I'd guess that a schema that includes all UIKit and MarkupKit types would be sufficient for most use cases. From my experience, custom views are not that common (except for table/collection view cells, but those are typically instantiated programmatically, not declaratively). What do you think?

aceontech commented 7 years ago

As I recall, the runtime provides only very limited type information (basically just numeric primitives - no classes or enumerated types).

I did a quick print-out of the property types available for the current schema (https://github.com/jarrroo/MarkupKitLint/blob/master/Tools/mkgen/Sources/Util/MKLInspector.m#L50):

2017-08-15 09:53:03.559 mkgen[59187:25054067] Type: Double
2017-08-15 09:53:03.560 mkgen[59187:25054067] Type: Int
2017-08-15 09:53:03.561 mkgen[59187:25054067] Type: Bool
2017-08-15 09:53:03.562 mkgen[59187:25054067] Type: UIViewController
2017-08-15 09:53:03.562 mkgen[59187:25054067] Type: NSString
2017-08-15 09:53:03.562 mkgen[59187:25054067] Type: UIView
2017-08-15 09:53:03.562 mkgen[59187:25054067] Type: Class
2017-08-15 09:53:03.563 mkgen[59187:25054067] Type: NSLayoutXAxisAnchor
2017-08-15 09:53:03.564 mkgen[59187:25054067] Type: NSLayoutYAxisAnchor
2017-08-15 09:53:03.564 mkgen[59187:25054067] Type: NSLayoutDimension
2017-08-15 09:53:03.566 mkgen[59187:25054067] Type: id
2017-08-15 09:53:03.567 mkgen[59187:25054067] Type: UILayoutGuide
2017-08-15 09:53:03.567 mkgen[59187:25054067] Type: UIStoryboardPreviewingSegueTemplateStorage
2017-08-15 09:53:03.567 mkgen[59187:25054067] Type: UIColor
2017-08-15 09:53:03.568 mkgen[59187:25054067] Type: CALayer
2017-08-15 09:53:03.569 mkgen[59187:25054067] Type: UITraitCollection
2017-08-15 09:53:03.569 mkgen[59187:25054067] Type: NSArray
2017-08-15 09:53:03.570 mkgen[59187:25054067] Type: CGPoint
2017-08-15 09:53:03.570 mkgen[59187:25054067] Type: CGRect
2017-08-15 09:53:03.570 mkgen[59187:25054067] Type: CGAffineTransform
2017-08-15 09:53:03.570 mkgen[59187:25054067] Type: UIBezierPath
2017-08-15 09:53:03.576 mkgen[59187:25054067] Type: AVPlayerLayer
2017-08-15 09:53:03.580 mkgen[59187:25054067] Type: id
2017-08-15 09:53:03.581 mkgen[59187:25054067] Type: CGSize
2017-08-15 09:53:03.581 mkgen[59187:25054067] Type: UIEdgeInsets
2017-08-15 09:53:03.585 mkgen[59187:25054067] Type: UIPanGestureRecognizer
2017-08-15 09:53:03.585 mkgen[59187:25054067] Type: UIPinchGestureRecognizer
2017-08-15 09:53:03.585 mkgen[59187:25054067] Type: UIGestureRecognizer
2017-08-15 09:53:03.586 mkgen[59187:25054067] Type: UIRefreshControl

As you can see, the list contains various primitive, struct and even object types. I think we can do a pretty good job of value validation. Some property types are id though, so it may not be perfect.

That's an interesting idea, but how would it work for untyped elements such as <segment>?

I think we can explicitly exclude elements like <segment>.

I'd guess that a schema that includes all UIKit and MarkupKit types would be sufficient for most use cases

That's what I'll do. It'll cover 90% of the cases, I agree.